@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c

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 (189) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +172 -50
  3. package/dist/bin/rango.js +138 -50
  4. package/dist/vite/index.js +1160 -508
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +17 -16
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/cache-guide/SKILL.md +32 -0
  10. package/skills/caching/SKILL.md +49 -8
  11. package/skills/document-cache/SKILL.md +2 -2
  12. package/skills/handler-use/SKILL.md +362 -0
  13. package/skills/hooks/SKILL.md +61 -51
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +20 -0
  16. package/skills/layout/SKILL.md +22 -0
  17. package/skills/links/SKILL.md +91 -17
  18. package/skills/loader/SKILL.md +107 -24
  19. package/skills/middleware/SKILL.md +34 -3
  20. package/skills/migrate-nextjs/SKILL.md +560 -0
  21. package/skills/migrate-react-router/SKILL.md +765 -0
  22. package/skills/parallel/SKILL.md +185 -0
  23. package/skills/prerender/SKILL.md +112 -70
  24. package/skills/rango/SKILL.md +24 -23
  25. package/skills/response-routes/SKILL.md +8 -0
  26. package/skills/route/SKILL.md +58 -4
  27. package/skills/router-setup/SKILL.md +95 -5
  28. package/skills/streams-and-websockets/SKILL.md +283 -0
  29. package/skills/typesafety/SKILL.md +38 -24
  30. package/src/__internal.ts +92 -0
  31. package/src/browser/app-shell.ts +52 -0
  32. package/src/browser/app-version.ts +14 -0
  33. package/src/browser/event-controller.ts +5 -0
  34. package/src/browser/link-interceptor.ts +4 -0
  35. package/src/browser/navigation-bridge.ts +175 -17
  36. package/src/browser/navigation-client.ts +177 -44
  37. package/src/browser/navigation-store.ts +68 -9
  38. package/src/browser/navigation-transaction.ts +11 -9
  39. package/src/browser/partial-update.ts +113 -17
  40. package/src/browser/prefetch/cache.ts +275 -28
  41. package/src/browser/prefetch/fetch.ts +191 -46
  42. package/src/browser/prefetch/policy.ts +6 -0
  43. package/src/browser/prefetch/queue.ts +123 -20
  44. package/src/browser/prefetch/resource-ready.ts +77 -0
  45. package/src/browser/rango-state.ts +53 -13
  46. package/src/browser/react/Link.tsx +98 -14
  47. package/src/browser/react/NavigationProvider.tsx +89 -14
  48. package/src/browser/react/context.ts +7 -2
  49. package/src/browser/react/use-handle.ts +9 -58
  50. package/src/browser/react/use-navigation.ts +22 -2
  51. package/src/browser/react/use-params.ts +11 -1
  52. package/src/browser/react/use-router.ts +29 -9
  53. package/src/browser/rsc-router.tsx +177 -66
  54. package/src/browser/scroll-restoration.ts +41 -42
  55. package/src/browser/segment-reconciler.ts +36 -9
  56. package/src/browser/server-action-bridge.ts +8 -6
  57. package/src/browser/types.ts +73 -5
  58. package/src/build/generate-manifest.ts +6 -6
  59. package/src/build/generate-route-types.ts +3 -0
  60. package/src/build/route-trie.ts +67 -25
  61. package/src/build/route-types/include-resolution.ts +8 -1
  62. package/src/build/route-types/router-processing.ts +223 -74
  63. package/src/build/route-types/scan-filter.ts +8 -1
  64. package/src/cache/cache-runtime.ts +15 -11
  65. package/src/cache/cache-scope.ts +48 -7
  66. package/src/cache/cf/cf-cache-store.ts +455 -15
  67. package/src/cache/cf/index.ts +5 -1
  68. package/src/cache/document-cache.ts +17 -7
  69. package/src/cache/index.ts +1 -0
  70. package/src/cache/taint.ts +55 -0
  71. package/src/client.rsc.tsx +2 -1
  72. package/src/client.tsx +85 -276
  73. package/src/context-var.ts +72 -2
  74. package/src/debug.ts +2 -2
  75. package/src/handle.ts +40 -0
  76. package/src/handles/breadcrumbs.ts +66 -0
  77. package/src/handles/index.ts +1 -0
  78. package/src/host/index.ts +0 -3
  79. package/src/index.rsc.ts +9 -36
  80. package/src/index.ts +79 -70
  81. package/src/outlet-context.ts +1 -1
  82. package/src/prerender/store.ts +57 -15
  83. package/src/prerender.ts +138 -77
  84. package/src/response-utils.ts +28 -0
  85. package/src/reverse.ts +27 -2
  86. package/src/route-definition/dsl-helpers.ts +240 -40
  87. package/src/route-definition/helpers-types.ts +67 -19
  88. package/src/route-definition/index.ts +3 -3
  89. package/src/route-definition/redirect.ts +11 -3
  90. package/src/route-definition/resolve-handler-use.ts +155 -0
  91. package/src/route-map-builder.ts +7 -1
  92. package/src/route-types.ts +18 -0
  93. package/src/router/content-negotiation.ts +100 -1
  94. package/src/router/find-match.ts +4 -2
  95. package/src/router/handler-context.ts +129 -26
  96. package/src/router/intercept-resolution.ts +11 -4
  97. package/src/router/lazy-includes.ts +10 -7
  98. package/src/router/loader-resolution.ts +160 -22
  99. package/src/router/logging.ts +5 -2
  100. package/src/router/manifest.ts +31 -16
  101. package/src/router/match-api.ts +128 -193
  102. package/src/router/match-middleware/background-revalidation.ts +30 -2
  103. package/src/router/match-middleware/cache-lookup.ts +94 -17
  104. package/src/router/match-middleware/cache-store.ts +53 -10
  105. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  106. package/src/router/match-middleware/segment-resolution.ts +61 -5
  107. package/src/router/match-result.ts +103 -18
  108. package/src/router/metrics.ts +238 -13
  109. package/src/router/middleware-types.ts +48 -27
  110. package/src/router/middleware.ts +201 -86
  111. package/src/router/navigation-snapshot.ts +182 -0
  112. package/src/router/pattern-matching.ts +77 -11
  113. package/src/router/prerender-match.ts +114 -10
  114. package/src/router/preview-match.ts +30 -102
  115. package/src/router/request-classification.ts +310 -0
  116. package/src/router/revalidation.ts +27 -7
  117. package/src/router/route-snapshot.ts +245 -0
  118. package/src/router/router-context.ts +6 -1
  119. package/src/router/router-interfaces.ts +50 -5
  120. package/src/router/router-options.ts +50 -19
  121. package/src/router/segment-resolution/fresh.ts +215 -19
  122. package/src/router/segment-resolution/helpers.ts +30 -25
  123. package/src/router/segment-resolution/loader-cache.ts +1 -0
  124. package/src/router/segment-resolution/revalidation.ts +454 -301
  125. package/src/router/segment-wrappers.ts +2 -0
  126. package/src/router/trie-matching.ts +30 -6
  127. package/src/router/types.ts +1 -0
  128. package/src/router/url-params.ts +49 -0
  129. package/src/router.ts +89 -17
  130. package/src/rsc/handler.ts +563 -364
  131. package/src/rsc/helpers.ts +69 -41
  132. package/src/rsc/index.ts +0 -20
  133. package/src/rsc/loader-fetch.ts +23 -3
  134. package/src/rsc/manifest-init.ts +5 -1
  135. package/src/rsc/progressive-enhancement.ts +37 -10
  136. package/src/rsc/response-route-handler.ts +14 -1
  137. package/src/rsc/rsc-rendering.ts +47 -44
  138. package/src/rsc/server-action.ts +24 -10
  139. package/src/rsc/ssr-setup.ts +128 -0
  140. package/src/rsc/types.ts +11 -1
  141. package/src/search-params.ts +16 -13
  142. package/src/segment-content-promise.ts +67 -0
  143. package/src/segment-loader-promise.ts +122 -0
  144. package/src/segment-system.tsx +109 -23
  145. package/src/server/context.ts +174 -19
  146. package/src/server/handle-store.ts +19 -0
  147. package/src/server/loader-registry.ts +9 -8
  148. package/src/server/request-context.ts +218 -65
  149. package/src/server.ts +6 -0
  150. package/src/ssr/index.tsx +4 -0
  151. package/src/static-handler.ts +18 -6
  152. package/src/theme/index.ts +4 -13
  153. package/src/types/cache-types.ts +4 -4
  154. package/src/types/handler-context.ts +140 -72
  155. package/src/types/loader-types.ts +41 -15
  156. package/src/types/request-scope.ts +126 -0
  157. package/src/types/route-config.ts +17 -8
  158. package/src/types/route-entry.ts +19 -1
  159. package/src/types/segments.ts +2 -5
  160. package/src/urls/include-helper.ts +24 -14
  161. package/src/urls/path-helper-types.ts +39 -6
  162. package/src/urls/path-helper.ts +48 -13
  163. package/src/urls/pattern-types.ts +12 -0
  164. package/src/urls/response-types.ts +18 -16
  165. package/src/use-loader.tsx +77 -5
  166. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  167. package/src/vite/discovery/discover-routers.ts +7 -4
  168. package/src/vite/discovery/prerender-collection.ts +162 -88
  169. package/src/vite/discovery/state.ts +17 -13
  170. package/src/vite/index.ts +8 -3
  171. package/src/vite/plugin-types.ts +51 -79
  172. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  173. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  174. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  175. package/src/vite/plugins/expose-action-id.ts +1 -3
  176. package/src/vite/plugins/expose-id-utils.ts +12 -0
  177. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  178. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  179. package/src/vite/plugins/performance-tracks.ts +88 -0
  180. package/src/vite/plugins/refresh-cmd.ts +127 -0
  181. package/src/vite/plugins/version-plugin.ts +13 -1
  182. package/src/vite/rango.ts +190 -217
  183. package/src/vite/router-discovery.ts +241 -45
  184. package/src/vite/utils/banner.ts +4 -4
  185. package/src/vite/utils/package-resolution.ts +34 -1
  186. package/src/vite/utils/prerender-utils.ts +97 -5
  187. package/src/vite/utils/shared-utils.ts +3 -2
  188. package/skills/testing/SKILL.md +0 -226
  189. package/src/route-definition/route-function.ts +0 -119
package/src/debug.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Debug utilities for manifest inspection and comparison
3
3
  */
4
4
 
5
- import type { EntryData } from "./server/context";
5
+ import { getParallelSlotCount, type EntryData } from "./server/context";
6
6
 
7
7
  /**
8
8
  * Serialized entry for debug output
@@ -64,7 +64,7 @@ export function serializeManifest(
64
64
  hasLoader: entry.loader?.length > 0,
65
65
  hasMiddleware: entry.middleware?.length > 0,
66
66
  hasErrorBoundary: entry.errorBoundary?.length > 0,
67
- parallelCount: entry.parallel?.length ?? 0,
67
+ parallelCount: getParallelSlotCount(entry.parallel),
68
68
  interceptCount: entry.intercept?.length ?? 0,
69
69
  };
70
70
 
package/src/handle.ts CHANGED
@@ -133,3 +133,43 @@ export function isHandle(value: unknown): value is Handle<unknown, unknown> {
133
133
  (value as { __brand: unknown }).__brand === "handle"
134
134
  );
135
135
  }
136
+
137
+ /**
138
+ * Collect handle data from a HandleData map, applying the handle's collect
139
+ * function over segments in order. Shared between server-side rendered()
140
+ * reads and client-side useHandle().
141
+ *
142
+ * @param handle - The handle to collect data for
143
+ * @param data - Full handle data map (handleName -> segmentId -> entries[])
144
+ * @param segmentOrder - Segment IDs in parent -> child resolution order
145
+ */
146
+ export function collectHandleData<TData, TAccumulated>(
147
+ handle: Handle<TData, TAccumulated>,
148
+ data: Record<string, Record<string, unknown[]>>,
149
+ segmentOrder: string[],
150
+ ): TAccumulated {
151
+ const collectFn = getCollectFn(handle.$$id);
152
+ if (!collectFn && process.env.NODE_ENV !== "production") {
153
+ console.warn(
154
+ `[rsc-router] Handle "${handle.$$id}" has no registered collect function. ` +
155
+ `Falling back to flat array. Ensure the handle module is imported so ` +
156
+ `createHandle() runs and registers the collect function.`,
157
+ );
158
+ }
159
+ const collect = (collectFn ??
160
+ (defaultCollect as unknown as (segments: unknown[][]) => unknown)) as (
161
+ segments: TData[][],
162
+ ) => TAccumulated;
163
+
164
+ const segmentData = data[handle.$$id];
165
+ if (!segmentData) return collect([]);
166
+
167
+ const segmentArrays: TData[][] = [];
168
+ for (const segmentId of segmentOrder) {
169
+ const entries = segmentData[segmentId];
170
+ if (entries && entries.length > 0) {
171
+ segmentArrays.push(entries as TData[]);
172
+ }
173
+ }
174
+ return collect(segmentArrays);
175
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Built-in Breadcrumbs handle for accumulating breadcrumb items across route segments.
3
+ *
4
+ * Each layout/route pushes breadcrumb items via `ctx.use(Breadcrumbs)`.
5
+ * Items are collected in parent-to-child order with automatic deduplication
6
+ * by `href` (last item for each href wins).
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // In route handler
11
+ * route("/blog/:slug", (ctx) => {
12
+ * const breadcrumb = ctx.use(Breadcrumbs);
13
+ * breadcrumb({ label: "Blog", href: "/blog" });
14
+ * breadcrumb({ label: post.title, href: `/blog/${ctx.params.slug}` });
15
+ * });
16
+ *
17
+ * // In client component (consume with useHandle)
18
+ * const crumbs = useHandle(Breadcrumbs);
19
+ * crumbs.map((c) => <a href={c.href}>{c.label}</a>);
20
+ * ```
21
+ */
22
+
23
+ import type { ReactNode } from "react";
24
+ import { createHandle, type Handle } from "../handle.js";
25
+
26
+ /**
27
+ * A single breadcrumb item.
28
+ *
29
+ * @property label - Display text for the breadcrumb
30
+ * @property href - URL the breadcrumb links to
31
+ * @property content - Optional extra content (sync or async) rendered alongside the label
32
+ */
33
+ export interface BreadcrumbItem {
34
+ label: string;
35
+ href: string;
36
+ content?: ReactNode | Promise<ReactNode>;
37
+ }
38
+
39
+ /**
40
+ * Collect function for Breadcrumbs handle.
41
+ * Flattens segments in parent-to-child order with deduplication by href
42
+ * (last item for each href wins).
43
+ */
44
+ function collectBreadcrumbs(segments: BreadcrumbItem[][]): BreadcrumbItem[] {
45
+ const all = segments.flat();
46
+ const seen = new Map<string, number>();
47
+
48
+ for (let i = 0; i < all.length; i++) {
49
+ seen.set(all[i].href, i);
50
+ }
51
+
52
+ // Return items in order, keeping only the last occurrence per href
53
+ return all.filter((item, index) => seen.get(item.href) === index);
54
+ }
55
+
56
+ /**
57
+ * Built-in handle for accumulating breadcrumb navigation items.
58
+ *
59
+ * Use `ctx.use(Breadcrumbs)` in route handlers to push breadcrumb items.
60
+ * Use `useHandle(Breadcrumbs)` in client components to consume them.
61
+ */
62
+ export const Breadcrumbs: Handle<BreadcrumbItem, BreadcrumbItem[]> =
63
+ createHandle<BreadcrumbItem, BreadcrumbItem[]>(
64
+ collectBreadcrumbs,
65
+ "__rsc_router_breadcrumbs__",
66
+ );
@@ -4,3 +4,4 @@
4
4
 
5
5
  export { Meta } from "./meta.ts";
6
6
  export { MetaTags } from "./MetaTags.tsx";
7
+ export { Breadcrumbs, type BreadcrumbItem } from "./breadcrumbs.ts";
package/src/host/index.ts CHANGED
@@ -25,9 +25,6 @@
25
25
  // Core router
26
26
  export { createHostRouter } from "./router.js";
27
27
 
28
- // Host router registry for build-time discovery
29
- export { HostRouterRegistry, type HostRouterRegistryEntry } from "./router.js";
30
-
31
28
  // Utilities
32
29
  export { defineHosts } from "./utils.js";
33
30
 
package/src/index.rsc.ts CHANGED
@@ -11,8 +11,6 @@
11
11
 
12
12
  // Re-export all universal exports from index.ts
13
13
  export {
14
- // Universal rendering utilities
15
- renderSegments,
16
14
  // Error classes
17
15
  RouteNotFoundError,
18
16
  DataNotFoundError,
@@ -21,9 +19,6 @@ export {
21
19
  HandlerError,
22
20
  BuildError,
23
21
  InvalidHandlerError,
24
- NetworkError,
25
- isNetworkError,
26
- sanitizeError,
27
22
  RouterError,
28
23
  Skip,
29
24
  isSkip,
@@ -40,7 +35,6 @@ export type {
40
35
  TrailingSlashMode,
41
36
  // Handler types
42
37
  Handler,
43
- ScopedRouteMap,
44
38
  HandlerContext,
45
39
  ExtractParams,
46
40
  GenericParams,
@@ -106,6 +100,7 @@ export type {
106
100
  LayoutUseItem,
107
101
  AllUseItems,
108
102
  UseItems,
103
+ HandlerUseItem,
109
104
  } from "./route-types.js";
110
105
 
111
106
  // Handle API
@@ -120,9 +115,9 @@ export { nonce } from "./rsc/nonce.js";
120
115
  // Pre-render handler API
121
116
  export {
122
117
  Prerender,
123
- isPrerenderHandler,
118
+ Passthrough,
124
119
  type PrerenderHandlerDefinition,
125
- type PrerenderPassthroughContext,
120
+ type PassthroughHandlerDefinition,
126
121
  type PrerenderOptions,
127
122
  type BuildContext,
128
123
  type StaticBuildContext,
@@ -130,16 +125,11 @@ export {
130
125
  } from "./prerender.js";
131
126
 
132
127
  // Static handler API
133
- export {
134
- Static,
135
- isStaticHandler,
136
- type StaticHandlerDefinition,
137
- } from "./static-handler.js";
128
+ export { Static, type StaticHandlerDefinition } from "./static-handler.js";
138
129
 
139
130
  // Django-style URL patterns (RSC/server context)
140
131
  export {
141
132
  urls,
142
- RESPONSE_TYPE,
143
133
  type PathHelpers,
144
134
  type PathOptions,
145
135
  type UrlPatterns,
@@ -171,6 +161,7 @@ export type { HandlerCacheConfig } from "./rsc/types.js";
171
161
 
172
162
  // Built-in handles (server-side)
173
163
  export { Meta } from "./handles/meta.js";
164
+ export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
174
165
 
175
166
  // Request context (for accessing request data in server actions/components).
176
167
  // Re-exported with a narrowed return type so that public consumers only see
@@ -181,6 +172,9 @@ export type { PublicRequestContext as RequestContext } from "./server/request-co
181
172
  import type { PublicRequestContext } from "./server/request-context.js";
182
173
  import type { DefaultEnv } from "./types/global-namespace.js";
183
174
 
175
+ // Shared base for every user-facing request context (mirrors index.ts).
176
+ export type { RequestScope, ExecutionContext } from "./types/request-scope.js";
177
+
184
178
  export const getRequestContext: <
185
179
  TEnv = DefaultEnv,
186
180
  >() => PublicRequestContext<TEnv> = _getRequestContextInternal;
@@ -206,8 +200,6 @@ export type {
206
200
  ReverseFunction,
207
201
  ExtractLocalRoutes,
208
202
  ParamsFor,
209
- SanitizePrefix,
210
- MergeRoutes,
211
203
  } from "./reverse.js";
212
204
  export { scopedReverse, createReverse } from "./reverse.js";
213
205
 
@@ -220,12 +212,6 @@ export type {
220
212
  RouteParams,
221
213
  } from "./search-params.js";
222
214
 
223
- // Debug utilities for route matching (development only)
224
- export {
225
- enableMatchDebug,
226
- getMatchDebugStats,
227
- } from "./router/pattern-matching.js";
228
-
229
215
  // Location state (universal)
230
216
  export {
231
217
  createLocationState,
@@ -241,20 +227,7 @@ export type { PathResponse } from "./href-client.js";
241
227
  export { createConsoleSink } from "./router/telemetry.js";
242
228
  export { createOTelSink } from "./router/telemetry-otel.js";
243
229
  export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
244
- export type {
245
- TelemetrySink,
246
- TelemetryEvent,
247
- RequestStartEvent,
248
- RequestEndEvent,
249
- RequestErrorEvent,
250
- RequestTimeoutEvent,
251
- LoaderStartEvent,
252
- LoaderEndEvent,
253
- LoaderErrorEvent,
254
- HandlerErrorEvent,
255
- CacheDecisionEvent,
256
- RevalidationDecisionEvent,
257
- } from "./router/telemetry.js";
230
+ export type { TelemetrySink, TelemetryEvent } from "./router/telemetry.js";
258
231
 
259
232
  // Timeout types and error class
260
233
  export { RouterTimeoutError } from "./router/timeout.js";
package/src/index.ts CHANGED
@@ -10,9 +10,6 @@
10
10
  * import from "@rangojs/router/client"
11
11
  */
12
12
 
13
- // Universal rendering utilities (work on both server and client)
14
- export { renderSegments } from "./segment-system.js";
15
-
16
13
  // Error classes (can be used on both server and client)
17
14
  export {
18
15
  RouteNotFoundError,
@@ -22,9 +19,6 @@ export {
22
19
  HandlerError,
23
20
  BuildError,
24
21
  InvalidHandlerError,
25
- NetworkError,
26
- isNetworkError,
27
- sanitizeError,
28
22
  RouterError,
29
23
  Skip,
30
24
  isSkip,
@@ -41,7 +35,6 @@ export type {
41
35
  TrailingSlashMode,
42
36
  // Handler types
43
37
  Handler, // Supports params object, path pattern, or route name
44
- ScopedRouteMap, // Scoped view of GeneratedRouteMap for Handler<"localName", ScopedRouteMap<"prefix">>
45
38
  HandlerContext,
46
39
  ExtractParams,
47
40
  GenericParams,
@@ -95,6 +88,7 @@ export type {
95
88
  LayoutUseItem,
96
89
  AllUseItems,
97
90
  UseItems,
91
+ HandlerUseItem,
98
92
  } from "./route-types.js";
99
93
 
100
94
  // Response route types (usable in both server and client contexts)
@@ -115,25 +109,32 @@ export type {
115
109
  // Middleware context types
116
110
  export type { MiddlewareContext, CookieOptions } from "./router/middleware.js";
117
111
 
112
+ function serverOnlyStubError(name: string): Error {
113
+ return new Error(
114
+ `${name}() is only available from "@rangojs/router" in a react-server/RSC environment. ` +
115
+ `For client hooks and components, import from "@rangojs/router/client".`,
116
+ );
117
+ }
118
+
118
119
  /**
119
120
  * Error-throwing stub for server-only `urls` function.
120
121
  */
121
122
  export function urls(): never {
122
- throw new Error("urls() is server-only and requires RSC context.");
123
+ throw serverOnlyStubError("urls");
123
124
  }
124
125
 
125
126
  /**
126
127
  * Error-throwing stub for server-only `createRouter` function.
127
128
  */
128
129
  export function createRouter(): never {
129
- throw new Error("createRouter() is server-only and requires RSC context.");
130
+ throw serverOnlyStubError("createRouter");
130
131
  }
131
132
 
132
133
  /**
133
134
  * Error-throwing stub for server-only `redirect` function.
134
135
  */
135
136
  export function redirect(): never {
136
- throw new Error("redirect() is server-only and requires RSC context.");
137
+ throw serverOnlyStubError("redirect");
137
138
  }
138
139
 
139
140
  // Handle API (universal - works on both server and client)
@@ -146,110 +147,126 @@ export { createVar, type ContextVar } from "./context-var.js";
146
147
  export { nonce } from "./rsc/nonce.js";
147
148
 
148
149
  /**
149
- * Error-throwing stub for server-only `Prerender` function.
150
+ * SSR/client stub for server-only `Prerender` function.
151
+ *
152
+ * Returns a lightweight stub object instead of throwing so that the
153
+ * production SSR build can safely bundle the RSC entry chunk — the SSR
154
+ * bundler resolves `@rangojs/router` to this (SSR) entry, so Prerender
155
+ * calls in RSC code must not crash at module-evaluation time.
150
156
  */
151
- export function Prerender(): never {
152
- throw new Error("Prerender() is server-only and requires RSC context.");
157
+ export function Prerender(
158
+ _handler?: any,
159
+ _optionsOrId?: any,
160
+ __injectedId?: string,
161
+ ): any {
162
+ const id =
163
+ typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
164
+ return { __brand: "prerenderHandler" as const, $$id: id };
153
165
  }
154
166
 
155
167
  /**
156
- * Error-throwing stub for server-only `Static` function.
168
+ * SSR/client stub for server-only `Passthrough` function.
157
169
  */
158
- export function Static(): never {
159
- throw new Error("Static() is server-only and requires RSC context.");
170
+ export function Passthrough(
171
+ _handler?: any,
172
+ _optionsOrId?: any,
173
+ __injectedId?: string,
174
+ ): any {
175
+ const id =
176
+ typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
177
+ return { __brand: "passthroughHandler" as const, $$id: id };
178
+ }
179
+
180
+ /**
181
+ * SSR/client stub for server-only `Static` function.
182
+ *
183
+ * Returns a lightweight stub object instead of throwing so that the
184
+ * production SSR build can safely bundle the RSC entry chunk — the SSR
185
+ * bundler resolves `@rangojs/router` to this (SSR) entry, so Static
186
+ * calls in RSC code must not crash at module-evaluation time.
187
+ */
188
+ export function Static(
189
+ _handler?: any,
190
+ _optionsOrId?: any,
191
+ __injectedId?: string,
192
+ ): any {
193
+ const id =
194
+ typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
195
+ return { __brand: "staticHandler" as const, $$id: id };
160
196
  }
161
197
 
162
198
  /**
163
199
  * Error-throwing stub for server-only `getRequestContext` function.
164
200
  */
165
201
  export function getRequestContext(): never {
166
- throw new Error(
167
- "getRequestContext() is server-only and requires RSC context.",
168
- );
202
+ throw serverOnlyStubError("getRequestContext");
169
203
  }
170
204
 
171
205
  /**
172
206
  * Error-throwing stub for server-only `cookies` function.
173
207
  */
174
208
  export function cookies(): never {
175
- throw new Error("cookies() is server-only and requires RSC context.");
209
+ throw serverOnlyStubError("cookies");
176
210
  }
177
211
 
178
212
  /**
179
213
  * Error-throwing stub for server-only `headers` function.
180
214
  */
181
215
  export function headers(): never {
182
- throw new Error("headers() is server-only and requires RSC context.");
216
+ throw serverOnlyStubError("headers");
183
217
  }
184
218
 
185
219
  /**
186
220
  * Error-throwing stub for server-only `createReverse` function.
187
221
  */
188
222
  export function createReverse(): never {
189
- throw new Error("createReverse() is server-only and requires RSC context.");
190
- }
191
-
192
- /**
193
- * Error-throwing stub for server-only `enableMatchDebug` function.
194
- */
195
- export function enableMatchDebug(): never {
196
- throw new Error(
197
- "enableMatchDebug() is server-only and requires RSC context.",
198
- );
199
- }
200
-
201
- /**
202
- * Error-throwing stub for server-only `getMatchDebugStats` function.
203
- */
204
- export function getMatchDebugStats(): never {
205
- throw new Error(
206
- "getMatchDebugStats() is server-only and requires RSC context.",
207
- );
223
+ throw serverOnlyStubError("createReverse");
208
224
  }
209
225
 
210
226
  // Error-throwing stubs for server-only route helpers
211
227
  export function layout(): never {
212
- throw new Error("layout() is server-only and requires RSC context.");
228
+ throw serverOnlyStubError("layout");
213
229
  }
214
230
  export function cache(): never {
215
- throw new Error("cache() is server-only and requires RSC context.");
231
+ throw serverOnlyStubError("cache");
216
232
  }
217
233
  export function middleware(): never {
218
- throw new Error("middleware() is server-only and requires RSC context.");
234
+ throw serverOnlyStubError("middleware");
219
235
  }
220
236
  export function revalidate(): never {
221
- throw new Error("revalidate() is server-only and requires RSC context.");
237
+ throw serverOnlyStubError("revalidate");
222
238
  }
223
239
  export function loader(): never {
224
- throw new Error("loader() is server-only and requires RSC context.");
240
+ throw serverOnlyStubError("loader");
225
241
  }
226
242
  export function loading(): never {
227
- throw new Error("loading() is server-only and requires RSC context.");
243
+ throw serverOnlyStubError("loading");
228
244
  }
229
245
  export function parallel(): never {
230
- throw new Error("parallel() is server-only and requires RSC context.");
246
+ throw serverOnlyStubError("parallel");
231
247
  }
232
248
  export function intercept(): never {
233
- throw new Error("intercept() is server-only and requires RSC context.");
249
+ throw serverOnlyStubError("intercept");
234
250
  }
235
251
  export function when(): never {
236
- throw new Error("when() is server-only and requires RSC context.");
252
+ throw serverOnlyStubError("when");
237
253
  }
238
254
  export function errorBoundary(): never {
239
- throw new Error("errorBoundary() is server-only and requires RSC context.");
255
+ throw serverOnlyStubError("errorBoundary");
240
256
  }
241
257
  export function notFoundBoundary(): never {
242
- throw new Error(
243
- "notFoundBoundary() is server-only and requires RSC context.",
244
- );
258
+ throw serverOnlyStubError("notFoundBoundary");
245
259
  }
246
260
  export function transition(): never {
247
- throw new Error("transition() is server-only and requires RSC context.");
261
+ throw serverOnlyStubError("transition");
248
262
  }
249
263
 
250
264
  // Request context type (safe for client)
251
265
  export type { PublicRequestContext as RequestContext } from "./server/request-context.js";
252
266
 
267
+ // Shared base for every user-facing request context.
268
+ export type { RequestScope, ExecutionContext } from "./types/request-scope.js";
269
+
253
270
  // Cookie store types (safe for client)
254
271
  export type {
255
272
  CookieStore,
@@ -257,17 +274,22 @@ export type {
257
274
  ReadonlyHeaders,
258
275
  } from "./server/cookie-store.js";
259
276
 
277
+ // Built-in handles (universal — work on both server and client)
278
+ export { Meta } from "./handles/meta.js";
279
+ export { Breadcrumbs } from "./handles/breadcrumbs.js";
280
+
260
281
  // Meta types
261
282
  export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
262
283
 
284
+ // Breadcrumb types
285
+ export type { BreadcrumbItem } from "./handles/breadcrumbs.js";
286
+
263
287
  // Reverse type utilities for type-safe URL generation (Django-style URL reversal)
264
288
  export type {
265
289
  ScopedReverseFunction,
266
290
  ReverseFunction,
267
291
  ExtractLocalRoutes,
268
292
  ParamsFor,
269
- SanitizePrefix,
270
- MergeRoutes,
271
293
  } from "./reverse.js";
272
294
  // scopedReverse() helper for handlers to get locally-typed reverse
273
295
  export { scopedReverse } from "./reverse.js";
@@ -287,20 +309,7 @@ export type { PathResponse } from "./href-client.js";
287
309
  export { createConsoleSink } from "./router/telemetry.js";
288
310
  export { createOTelSink } from "./router/telemetry-otel.js";
289
311
  export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
290
- export type {
291
- TelemetrySink,
292
- TelemetryEvent,
293
- RequestStartEvent,
294
- RequestEndEvent,
295
- RequestErrorEvent,
296
- RequestTimeoutEvent,
297
- LoaderStartEvent,
298
- LoaderEndEvent,
299
- LoaderErrorEvent,
300
- HandlerErrorEvent,
301
- CacheDecisionEvent,
302
- RevalidationDecisionEvent,
303
- } from "./router/telemetry.js";
312
+ export type { TelemetrySink, TelemetryEvent } from "./router/telemetry.js";
304
313
 
305
314
  // Timeout types and error class
306
315
  export { RouterTimeoutError } from "./router/timeout.js";
@@ -1,4 +1,4 @@
1
- import { Context, createContext, type ReactNode } from "react";
1
+ import { type Context, createContext, type ReactNode } from "react";
2
2
  import type { ResolvedSegment } from "./types";
3
3
 
4
4
  export interface OutletContextValue {
@@ -2,9 +2,10 @@
2
2
  * Prerender Store
3
3
  *
4
4
  * Reads pre-rendered segment data from the worker bundle at build time.
5
- * The data is stored as globalThis.__PRERENDER_MANIFEST, a map of
6
- * "<routeName>/<paramHash>" to dynamic import functions that resolve
7
- * individual prerender entry modules.
5
+ * The manifest module is lazily loaded via globalThis.__loadPrerenderManifestModule,
6
+ * a function injected into the RSC entry that returns the manifest module
7
+ * containing a key-to-specifier map and a `loadPrerenderAsset` function
8
+ * that anchors import() resolution relative to the manifest file.
8
9
  */
9
10
 
10
11
  import type {
@@ -34,11 +35,20 @@ export interface StaticStore {
34
35
  get(handlerId: string): Promise<StaticEntry | null>;
35
36
  }
36
37
 
38
+ interface PrerenderManifestModule {
39
+ default: Record<string, string>;
40
+ loadPrerenderAsset: (
41
+ specifier: string,
42
+ ) => Promise<{ default: PrerenderEntry }>;
43
+ }
44
+
37
45
  declare global {
38
- // Injected by closeBundle post-processing: map of key -> () => import("./assets/__pr-*.js")
46
+ // Injected by closeBundle post-processing: lazy loader for the prerender
47
+ // manifest module. The module exports a key→specifier map and a
48
+ // loadPrerenderAsset function that anchors import() relative to the manifest.
39
49
  // eslint-disable-next-line no-var
40
- var __PRERENDER_MANIFEST:
41
- | Record<string, () => Promise<{ default: PrerenderEntry }>>
50
+ var __loadPrerenderManifestModule:
51
+ | (() => Promise<PrerenderManifestModule>)
42
52
  | undefined;
43
53
  // Injected by closeBundle post-processing: map of handlerId -> () => import("./assets/__st-*.js")
44
54
  // Asset default export is either a string (no handles) or { encoded, handles } object.
@@ -78,17 +88,28 @@ export function createDevPrerenderStore(devUrl: string): PrerenderStore {
78
88
  /**
79
89
  * Create a prerender store.
80
90
  * Dev mode: on-demand fetch from Vite dev server (node:fs works there).
81
- * Production: backed by globalThis.__PRERENDER_MANIFEST injected at build time.
91
+ * Production: backed by globalThis.__loadPrerenderManifestModule which lazily
92
+ * loads the manifest module on first access.
82
93
  * Returns null if no prerender data is available.
83
94
  */
84
95
  export function createPrerenderStore(): PrerenderStore | null {
85
96
  if (globalThis.__PRERENDER_DEV_URL) {
86
97
  return createDevPrerenderStore(globalThis.__PRERENDER_DEV_URL);
87
98
  }
88
- const manifest = globalThis.__PRERENDER_MANIFEST;
89
- if (!manifest || Object.keys(manifest).length === 0) return null;
99
+ if (!globalThis.__loadPrerenderManifestModule) return null;
90
100
 
91
101
  const cache = new Map<string, Promise<PrerenderEntry | null>>();
102
+ let manifestModulePromise: Promise<PrerenderManifestModule | null> | null =
103
+ null;
104
+
105
+ function loadManifestModule(): Promise<PrerenderManifestModule | null> {
106
+ if (!manifestModulePromise) {
107
+ manifestModulePromise = globalThis.__loadPrerenderManifestModule!().catch(
108
+ () => null,
109
+ );
110
+ }
111
+ return manifestModulePromise;
112
+ }
92
113
 
93
114
  return {
94
115
  get(routeName: string, paramHash: string): Promise<PrerenderEntry | null> {
@@ -96,18 +117,39 @@ export function createPrerenderStore(): PrerenderStore | null {
96
117
  const cached = cache.get(key);
97
118
  if (cached) return cached;
98
119
 
99
- const loader = manifest[key];
100
- if (!loader) return Promise.resolve(null);
101
-
102
- const promise = loader()
103
- .then((mod) => mod.default)
104
- .catch(() => null);
120
+ const promise = loadManifestModule().then((mod) => {
121
+ if (!mod) return null;
122
+ const specifier = mod.default[key];
123
+ if (!specifier) return null;
124
+ // Let asset load errors propagate — a missing/corrupted artifact
125
+ // for a key that exists in the manifest is a build/deploy error
126
+ // and should surface as a 500, not be silently swallowed as null
127
+ // (which the handler stub would misreport as a 404).
128
+ return mod.loadPrerenderAsset(specifier).then((asset) => asset.default);
129
+ });
105
130
  cache.set(key, promise);
106
131
  return promise;
107
132
  },
108
133
  };
109
134
  }
110
135
 
136
+ /**
137
+ * Load the prerender manifest index for test introspection.
138
+ * Returns the key→specifier map or null if unavailable.
139
+ */
140
+ export async function loadPrerenderManifestIndex(): Promise<Record<
141
+ string,
142
+ string
143
+ > | null> {
144
+ if (!globalThis.__loadPrerenderManifestModule) return null;
145
+ try {
146
+ const mod = await globalThis.__loadPrerenderManifestModule();
147
+ return mod.default;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
111
153
  /**
112
154
  * Create a static segment store.
113
155
  * Production only: backed by globalThis.__STATIC_MANIFEST injected at build time.