@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +120 -204
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +190 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +63 -24
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +338 -126
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -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 {
@@ -5,11 +5,7 @@ import { OutletContext, type OutletContextValue } from "./outlet-context.js";
5
5
  import type { ResolvedSegment } from "./types.js";
6
6
 
7
7
  /**
8
- * Provider for outlet content - used internally by renderSegments
9
- *
10
- * Stores a reference to parent context so useLoader can walk up the chain
11
- * to find loader data from parent layouts. If this segment defines a loading
12
- * component, Outlet will wrap content with Suspense using that as fallback.
8
+ * Outlet content provider stores parent context for useLoader chain walking.
13
9
  */
14
10
  export function OutletProvider({
15
11
  content,
@@ -1,18 +1,10 @@
1
1
  /**
2
2
  * Deterministic param hashing for prerender storage keys.
3
- *
4
- * Used at build time (child process) to generate filenames and at
5
- * runtime (worker) to look up pre-rendered data. Both environments
6
- * must produce identical hashes for the same params.
7
- *
8
- * Uses a simple DJB2-based hash that works in all JS environments
9
- * (Node.js, Cloudflare Workers, browsers) without crypto imports.
3
+ * Used at build time and runtime; both must produce identical hashes.
4
+ * DJB2-based; works in all JS environments without crypto imports.
10
5
  */
11
6
 
12
- /**
13
- * Compute a deterministic hash string from route params.
14
- * For static routes (no params), returns "_".
15
- */
7
+ // For static routes (no params), returns "_".
16
8
  export function hashParams(params: Record<string, string>): string {
17
9
  const entries = Object.entries(params);
18
10
  if (entries.length === 0) return "_";
@@ -27,6 +19,13 @@ export function hashParams(params: Record<string, string>): string {
27
19
  /**
28
20
  * DJB2 hash returning an 8-char hex string.
29
21
  * Deterministic across all JS runtimes.
22
+ *
23
+ * 32-bit output: per-route collision probability hits ~50% near ~77k distinct
24
+ * param sets (birthday bound). The production store keys solely on
25
+ * routeName/paramHash and does not verify the canonical param string, so a
26
+ * collision serves the surviving entry for both param sets. Benign for typical
27
+ * catalogs; revisit (wider hash or stored-param verification) before
28
+ * pre-rendering hundreds of thousands of pages per route.
30
29
  */
31
30
  function djb2Hex(str: string): string {
32
31
  let hash = 5381;
@@ -1,21 +1,17 @@
1
1
  /**
2
- * Prerender Store
3
- *
4
- * Reads pre-rendered segment data from the worker bundle at build time.
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.
2
+ * Prerender Store — reads pre-rendered segment data from the worker bundle.
3
+ * Manifest module (injected via globalThis.__loadPrerenderManifestModule)
4
+ * contains key-to-specifier map and loadPrerenderAsset for import() resolution.
9
5
  */
10
6
 
11
- import type {
12
- SerializedSegmentData,
13
- SegmentHandleData,
14
- } from "../cache/types.js";
7
+ import type { SerializedSegmentData } from "../cache/types.js";
15
8
 
16
9
  export interface PrerenderEntry {
17
10
  segments: SerializedSegmentData[];
18
- handles: Record<string, SegmentHandleData>;
11
+ /** RSC-encoded handle map (see handle-snapshot.ts encodeHandles); "" when the
12
+ * route pushed no handles. Encoded so Promise/ReactNode handle values survive
13
+ * the JSON-serialized build artifact / dev wire, identical to the runtime cache. */
14
+ handles: string;
19
15
  }
20
16
 
21
17
  export interface PrerenderStore {
@@ -28,7 +24,9 @@ export interface PrerenderStore {
28
24
 
29
25
  export interface StaticEntry {
30
26
  encoded: string;
31
- handles: Record<string, unknown[]>;
27
+ /** RSC-encoded single-segment handle data (see encodeHandleValue); "" when the
28
+ * Static handler pushed no handles. */
29
+ handles: string;
32
30
  }
33
31
 
34
32
  export interface StaticStore {
@@ -99,13 +97,20 @@ export function createPrerenderStore(): PrerenderStore | null {
99
97
  if (!globalThis.__loadPrerenderManifestModule) return null;
100
98
 
101
99
  const cache = new Map<string, Promise<PrerenderEntry | null>>();
102
- let manifestModulePromise: Promise<PrerenderManifestModule | null> | null =
103
- null;
100
+ let manifestModulePromise: Promise<PrerenderManifestModule> | null = null;
104
101
 
105
- function loadManifestModule(): Promise<PrerenderManifestModule | null> {
102
+ function loadManifestModule(): Promise<PrerenderManifestModule> {
106
103
  if (!manifestModulePromise) {
104
+ // Do not cache a failed manifest-module load: clear the memoized promise
105
+ // on rejection so the next get() retries, and let the error propagate
106
+ // (consistent with the per-asset load policy below) instead of caching a
107
+ // null for the isolate lifetime, which would silently degrade every
108
+ // prerendered route to a miss after one transient failure.
107
109
  manifestModulePromise = globalThis.__loadPrerenderManifestModule!().catch(
108
- () => null,
110
+ (err) => {
111
+ manifestModulePromise = null;
112
+ throw err;
113
+ },
109
114
  );
110
115
  }
111
116
  return manifestModulePromise;
@@ -118,37 +123,28 @@ export function createPrerenderStore(): PrerenderStore | null {
118
123
  if (cached) return cached;
119
124
 
120
125
  const promise = loadManifestModule().then((mod) => {
121
- if (!mod) return null;
122
126
  const specifier = mod.default[key];
123
127
  if (!specifier) return null;
124
- return mod
125
- .loadPrerenderAsset(specifier)
126
- .then((asset) => asset.default)
127
- .catch(() => null);
128
+ // Let asset load errors propagate — a missing/corrupted artifact
129
+ // for a key that exists in the manifest is a build/deploy error
130
+ // and should surface as a 500, not be silently swallowed as null
131
+ // (which the handler stub would misreport as a 404).
132
+ return mod.loadPrerenderAsset(specifier).then((asset) => asset.default);
128
133
  });
129
- cache.set(key, promise);
134
+ // Only memoize once the manifest module resolved: a manifest-load
135
+ // rejection must not poison the per-key cache, or the retry above is moot.
136
+ cache.set(
137
+ key,
138
+ promise.catch((err) => {
139
+ cache.delete(key);
140
+ throw err;
141
+ }),
142
+ );
130
143
  return promise;
131
144
  },
132
145
  };
133
146
  }
134
147
 
135
- /**
136
- * Load the prerender manifest index for test introspection.
137
- * Returns the key→specifier map or null if unavailable.
138
- */
139
- export async function loadPrerenderManifestIndex(): Promise<Record<
140
- string,
141
- string
142
- > | null> {
143
- if (!globalThis.__loadPrerenderManifestModule) return null;
144
- try {
145
- const mod = await globalThis.__loadPrerenderManifestModule();
146
- return mod.default;
147
- } catch {
148
- return null;
149
- }
150
- }
151
-
152
148
  /**
153
149
  * Create a static segment store.
154
150
  * Production only: backed by globalThis.__STATIC_MANIFEST injected at build time.
@@ -173,7 +169,7 @@ export function createStaticStore(): StaticStore | null {
173
169
  const val = mod.default;
174
170
  // Normalize: string-only (no handles) or { encoded, handles }
175
171
  if (typeof val === "string") {
176
- return { encoded: val, handles: {} } as StaticEntry;
172
+ return { encoded: val, handles: "" } as StaticEntry;
177
173
  }
178
174
  return val as StaticEntry;
179
175
  })
package/src/prerender.ts CHANGED
@@ -36,7 +36,9 @@ import type { Handle } from "./handle.js";
36
36
  import type { ContextVar } from "./context-var.js";
37
37
  import type { ReverseFunction } from "./reverse.js";
38
38
  import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
39
+ import type { UseItems, HandlerUseItem } from "./route-types.js";
39
40
  import { isCachedFunction } from "./cache/taint.js";
41
+ import { isUnderTestRunner } from "./runtime-env.js";
40
42
 
41
43
  // -- Named route resolution types -------------------------------------------
42
44
 
@@ -68,9 +70,9 @@ type BuildReverseFunction = [DefaultReverseRouteMap] extends [
68
70
  * Default route map for Prerender named route resolution.
69
71
  * Uses GeneratedRouteMap (from gen file) to avoid circular dependencies.
70
72
  */
71
- type DefaultPrerenderRouteMap = keyof RSCRouter.GeneratedRouteMap extends never
73
+ type DefaultPrerenderRouteMap = keyof Rango.GeneratedRouteMap extends never
72
74
  ? {}
73
- : RSCRouter.GeneratedRouteMap;
75
+ : Rango.GeneratedRouteMap;
74
76
 
75
77
  /** Extract params from a route map entry (string pattern or { path } object). */
76
78
  type ExtractParamsFromEntry<TEntry> = TEntry extends string
@@ -105,13 +107,6 @@ type ResolvePrerenderParams<
105
107
  // -- Types ------------------------------------------------------------------
106
108
 
107
109
  export interface PrerenderOptions {
108
- /**
109
- * Keep handler in server bundle for live fallback (default: false).
110
- * false: handler replaced with stub, source-only APIs excluded from bundle.
111
- * true: handler stays in bundle, unknown params render live at request time.
112
- */
113
- passthrough?: boolean;
114
-
115
110
  /**
116
111
  * Maximum number of param sets to render in parallel (default: 1).
117
112
  * Only applies to dynamic Prerender handlers with getParams().
@@ -131,8 +126,8 @@ export interface PrerenderOptions {
131
126
 
132
127
  /**
133
128
  * Context passed to Prerender() handlers at build time.
134
- * Has a synthetic URL from getParams, params, and pathname.
135
- * No request, env, headers, cookies.
129
+ * Has a synthetic URL from getParams, params, pathname, and optionally env.
130
+ * No request, headers, cookies.
136
131
  */
137
132
  export interface BuildContext<TParams> {
138
133
  /** Params extracted from the route pattern (populated from getParams). */
@@ -141,6 +136,23 @@ export interface BuildContext<TParams> {
141
136
  /** True during build-time pre-rendering, false during passthrough live render. */
142
137
  build: true;
143
138
 
139
+ /**
140
+ * True when running in Vite dev mode (on-demand prerender), false during
141
+ * production `vite build`. Use this to branch on runtime mode without
142
+ * changing build semantics.
143
+ */
144
+ dev: boolean;
145
+
146
+ /**
147
+ * Build-time environment bindings (KV, D1, etc.) supplied by the Vite plugin.
148
+ * Only available when `buildEnv` is configured in rango() options.
149
+ * Throws with a clear error if not configured.
150
+ *
151
+ * This is NOT the live request env — it is shared across all prerender
152
+ * invocations for the build.
153
+ */
154
+ env: DefaultEnv;
155
+
144
156
  /** Read a variable set by getParams or a parent handler. */
145
157
  get: {
146
158
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -173,8 +185,8 @@ export interface BuildContext<TParams> {
173
185
 
174
186
  /**
175
187
  * Signal that this param set should not produce a local prerender artifact.
176
- * At runtime the handler runs live instead. Only valid on routes declared
177
- * with `{ passthrough: true }`.
188
+ * At runtime the live handler runs instead. Only valid on routes wrapped
189
+ * with `Passthrough()`.
178
190
  */
179
191
  passthrough: () => PrerenderPassthroughResult;
180
192
  }
@@ -187,6 +199,17 @@ export interface StaticBuildContext {
187
199
  /** Always true for Static handlers at build time. */
188
200
  build: true;
189
201
 
202
+ /**
203
+ * True when running in Vite dev mode, false during production build.
204
+ */
205
+ dev: boolean;
206
+
207
+ /**
208
+ * Build-time environment bindings supplied by the Vite plugin.
209
+ * Only available when `buildEnv` is configured in rango() options.
210
+ */
211
+ env: DefaultEnv;
212
+
190
213
  /** Read a variable (available for type consistency with BuildContext). */
191
214
  get: {
192
215
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -214,6 +237,17 @@ export interface GetParamsContext {
214
237
  /** Always true during build-time getParams execution. */
215
238
  build: true;
216
239
 
240
+ /**
241
+ * True when running in Vite dev mode, false during production build.
242
+ */
243
+ dev: boolean;
244
+
245
+ /**
246
+ * Build-time environment bindings supplied by the Vite plugin.
247
+ * Only available when `buildEnv` is configured in rango() options.
248
+ */
249
+ env: DefaultEnv;
250
+
217
251
  /** Set a variable that will be available to each handler invocation via ctx.get(). */
218
252
  set: {
219
253
  <T>(contextVar: ContextVar<T>, value: T): void;
@@ -224,23 +258,6 @@ export interface GetParamsContext {
224
258
  reverse: BuildReverseFunction;
225
259
  }
226
260
 
227
- /**
228
- * Context type for passthrough Prerender handlers.
229
- *
230
- * When `passthrough: true`, the handler runs both at build time and at request
231
- * time. The context is a full `HandlerContext` with `build: boolean`:
232
- * - `ctx.build === true`: build-time, env/request/res throw at runtime
233
- * - `ctx.build === false`: live request, full context available
234
- *
235
- * For `passthrough: false` (default), handlers receive `BuildContext` only.
236
- */
237
- export type PrerenderPassthroughContext<
238
- TParams = {},
239
- TEnv = DefaultEnv,
240
- > = HandlerContext<TParams, TEnv> & {
241
- passthrough: () => PrerenderPassthroughResult;
242
- };
243
-
244
261
  export interface PrerenderHandlerDefinition<
245
262
  TParams extends Record<string, any> = any,
246
263
  > {
@@ -253,8 +270,15 @@ export interface PrerenderHandlerDefinition<
253
270
  getParams?: (ctx: GetParamsContext) => Promise<TParams[]> | TParams[];
254
271
  /** Pre-render options. */
255
272
  options?: PrerenderOptions;
273
+ /** Composable default DSL items merged when the handler is mounted. */
274
+ use?: () => UseItems<HandlerUseItem>;
256
275
  }
257
276
 
277
+ // Process-stable fallback id counter (mirrors createHandle / createLoader). Only
278
+ // assigned in a bare unit test where the Vite plugin did not inject an id; never
279
+ // fires in a real build (the plugin always injects).
280
+ let runtimePrerenderIdCounter = 0;
281
+
258
282
  // -- Overloads --------------------------------------------------------------
259
283
  //
260
284
  // T accepts: named route string (global or .local) OR explicit param object.
@@ -263,7 +287,7 @@ export interface PrerenderHandlerDefinition<
263
287
  // Explicit params work as before:
264
288
  // Prerender<{ slug: string }> → params = { slug: string }
265
289
 
266
- // Overload 1: Static handler, no passthrough (build-time only)
290
+ // Overload 1: Static handler (build-time only)
267
291
  export function Prerender<
268
292
  T extends
269
293
  | keyof DefaultPrerenderRouteMap
@@ -273,34 +297,15 @@ export function Prerender<
273
297
  >(
274
298
  handler: (
275
299
  ctx: BuildContext<ResolvePrerenderParams<T, TRouteMap>>,
276
- ) => ReactNode | Promise<ReactNode>,
277
- options?: PrerenderOptions & { passthrough?: false },
278
- __injectedId?: string,
279
- ): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
280
-
281
- // Overload 2: Static handler, passthrough (build + live — full HandlerContext)
282
- export function Prerender<
283
- T extends
284
- | keyof DefaultPrerenderRouteMap
285
- | `.${keyof TRouteMap & string}`
286
- | Record<string, any> = {},
287
- TRouteMap extends {} = DefaultPrerenderRouteMap,
288
- TEnv = DefaultEnv,
289
- >(
290
- handler: (
291
- ctx: PrerenderPassthroughContext<
292
- ResolvePrerenderParams<T, TRouteMap>,
293
- TEnv
294
- >,
295
300
  ) =>
296
301
  | ReactNode
297
302
  | PrerenderPassthroughResult
298
303
  | Promise<ReactNode | PrerenderPassthroughResult>,
299
- options: PrerenderOptions & { passthrough: true },
304
+ options?: PrerenderOptions,
300
305
  __injectedId?: string,
301
306
  ): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
302
307
 
303
- // Overload 3: Dynamic handler, no passthrough (build-time only)
308
+ // Overload 2: Dynamic handler (build-time only)
304
309
  export function Prerender<
305
310
  T extends
306
311
  | keyof DefaultPrerenderRouteMap
@@ -315,35 +320,11 @@ export function Prerender<
315
320
  | ResolvePrerenderParams<T, TRouteMap>[],
316
321
  handler: (
317
322
  ctx: BuildContext<ResolvePrerenderParams<T, TRouteMap>>,
318
- ) => ReactNode | Promise<ReactNode>,
319
- options?: PrerenderOptions & { passthrough?: false },
320
- __injectedId?: string,
321
- ): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
322
-
323
- // Overload 4: Dynamic handler, passthrough (build + live — full HandlerContext)
324
- export function Prerender<
325
- T extends
326
- | keyof DefaultPrerenderRouteMap
327
- | `.${keyof TRouteMap & string}`
328
- | Record<string, any>,
329
- TRouteMap extends {} = DefaultPrerenderRouteMap,
330
- TEnv = DefaultEnv,
331
- >(
332
- getParams: (
333
- ctx: GetParamsContext,
334
- ) =>
335
- | Promise<ResolvePrerenderParams<T, TRouteMap>[]>
336
- | ResolvePrerenderParams<T, TRouteMap>[],
337
- handler: (
338
- ctx: PrerenderPassthroughContext<
339
- ResolvePrerenderParams<T, TRouteMap>,
340
- TEnv
341
- >,
342
323
  ) =>
343
324
  | ReactNode
344
325
  | PrerenderPassthroughResult
345
326
  | Promise<ReactNode | PrerenderPassthroughResult>,
346
- options: PrerenderOptions & { passthrough: true },
327
+ options?: PrerenderOptions,
347
328
  __injectedId?: string,
348
329
  ): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
349
330
 
@@ -401,12 +382,27 @@ export function Prerender<TParams extends Record<string, any>>(
401
382
  );
402
383
  }
403
384
 
404
- if (!id) {
385
+ // Throw unless under a test runner. The plugin always injects $$id for a
386
+ // supported `export const` Prerender on every build, so a missing id means
387
+ // either no plugin (a bare test — fall back below) or an UNSUPPORTED shape the
388
+ // plugin silently skipped (dev OR a real build — fail loud; a synthetic id
389
+ // would degrade to a silent prerender miss). The message is already small (no
390
+ // stack-parsing diagnostic), so it ships as-is. isUnderTestRunner() is
391
+ // runtime-safe — never a bare `process.env` access.
392
+ if (!id && !isUnderTestRunner()) {
405
393
  throw new Error(
406
- "[rsc-router] Prerender: missing $$id. " +
407
- "Ensure the exposeInternalIds Vite plugin is configured.",
394
+ "[rango] Prerender: missing $$id. Use `export const X = Prerender(...)` " +
395
+ "and ensure the exposeInternalIds Vite plugin is configured.",
408
396
  );
409
397
  }
398
+ // Under vitest with no plugin id: assign a process-stable runtime id so a
399
+ // whole-app router with Prerender routes constructs in a bare test (for
400
+ // dispatch / assertGeneratedRoutesMatch). Never reached in a real build (the
401
+ // throw above fires there); prerender storage/lookup keys on routeName +
402
+ // paramHash, never $$id (mirrors createHandle / createLoader).
403
+ if (!id) {
404
+ id = `__rango_runtime_prerender_${runtimePrerenderIdCounter++}`;
405
+ }
410
406
 
411
407
  return {
412
408
  __brand: "prerenderHandler" as const,
@@ -422,7 +418,7 @@ export function Prerender<TParams extends Record<string, any>>(
422
418
  /**
423
419
  * Sentinel returned by `ctx.passthrough()` to signal that a specific param set
424
420
  * should not produce a local prerender artifact. The build skips writing the
425
- * entry; at runtime the handler runs live (requires `{ passthrough: true }`).
421
+ * entry; at runtime the Passthrough live handler runs instead.
426
422
  */
427
423
  export const PRERENDER_PASSTHROUGH: Readonly<{
428
424
  __brand: "prerenderPassthrough";
@@ -446,7 +442,41 @@ export function isPrerenderPassthrough(
446
442
  );
447
443
  }
448
444
 
449
- // -- Type guard -------------------------------------------------------------
445
+ /**
446
+ * Detect whether any resolved segment carries the passthrough sentinel.
447
+ *
448
+ * A build handler signals passthrough by returning `ctx.passthrough()` (the
449
+ * PRERENDER_PASSTHROUGH sentinel), which lands on the segment's `component`.
450
+ * But when the route declares `loading()`, the handler result is deferred
451
+ * upstream (segment-resolution/fresh.ts), so `component` is a thenable resolving
452
+ * to the sentinel rather than the sentinel itself — a synchronous
453
+ * `isPrerenderPassthrough(component)` on the Promise returns false and the build
454
+ * bakes a corrupt artifact instead of deferring. Resolve thenables first.
455
+ *
456
+ * Rejections are swallowed here: a throwing build handler resurfaces during
457
+ * segment serialization, preserving the prior error-handling behavior.
458
+ */
459
+ export async function detectPrerenderPassthrough(
460
+ segments: ReadonlyArray<{ component: unknown }>,
461
+ ): Promise<boolean> {
462
+ for (const seg of segments) {
463
+ let component: unknown = seg.component;
464
+ if (
465
+ component &&
466
+ typeof (component as { then?: unknown }).then === "function"
467
+ ) {
468
+ try {
469
+ component = await component;
470
+ } catch {
471
+ continue;
472
+ }
473
+ }
474
+ if (isPrerenderPassthrough(component)) return true;
475
+ }
476
+ return false;
477
+ }
478
+
479
+ // -- Type guards ------------------------------------------------------------
450
480
 
451
481
  /**
452
482
  * Type guard to check if a value is a PrerenderHandlerDefinition.
@@ -461,3 +491,89 @@ export function isPrerenderHandler(
461
491
  (value as { __brand: unknown }).__brand === "prerenderHandler"
462
492
  );
463
493
  }
494
+
495
+ // -- Passthrough wrapper ----------------------------------------------------
496
+
497
+ /**
498
+ * A prerender route with a live fallback handler for unknown params at runtime.
499
+ *
500
+ * Wraps a `Prerender(...)` definition with a separate handler that runs at
501
+ * request time for params not covered by `getParams()`.
502
+ *
503
+ * - Build time: `prerenderDef` provides getParams + build handler.
504
+ * - Runtime: `liveHandler` runs for unknown params with full HandlerContext.
505
+ *
506
+ * @example
507
+ * ```ts
508
+ * const BlogPrerender = Prerender(
509
+ * async () => [{ slug: "getting-started" }, { slug: "api-reference" }],
510
+ * async (ctx) => <BlogPost slug={ctx.params.slug} />,
511
+ * );
512
+ *
513
+ * // In route definition:
514
+ * path("/blog/:slug", Passthrough(BlogPrerender, async (ctx) => {
515
+ * const post = await ctx.env.DB.get(ctx.params.slug);
516
+ * return <BlogPost slug={ctx.params.slug} post={post} />;
517
+ * }))
518
+ * ```
519
+ */
520
+ export interface PassthroughHandlerDefinition<
521
+ TParams extends Record<string, any> = any,
522
+ TEnv = DefaultEnv,
523
+ > {
524
+ readonly __brand: "passthroughHandler";
525
+ /** The underlying prerender definition (build-time rendering). */
526
+ prerenderDef: PrerenderHandlerDefinition<TParams>;
527
+ /** Live handler for runtime fallback on unknown params. */
528
+ liveHandler: (
529
+ ctx: HandlerContext<TParams, TEnv>,
530
+ ) => ReactNode | Promise<ReactNode> | Response | Promise<Response>;
531
+ /** Composable default DSL items merged when the handler is mounted. */
532
+ use?: () => UseItems<HandlerUseItem>;
533
+ }
534
+
535
+ export function Passthrough<
536
+ TParams extends Record<string, any>,
537
+ TEnv = DefaultEnv,
538
+ >(
539
+ prerenderDef: PrerenderHandlerDefinition<TParams>,
540
+ liveHandler: (
541
+ ctx: HandlerContext<TParams, TEnv>,
542
+ ) => ReactNode | Promise<ReactNode> | Response | Promise<Response>,
543
+ ): PassthroughHandlerDefinition<TParams, TEnv>;
544
+
545
+ // Implementation
546
+ export function Passthrough<
547
+ TParams extends Record<string, any>,
548
+ TEnv = DefaultEnv,
549
+ >(
550
+ prerenderDef: PrerenderHandlerDefinition<TParams>,
551
+ liveHandler: (
552
+ ctx: HandlerContext<TParams, TEnv>,
553
+ ) => ReactNode | Promise<ReactNode> | Response | Promise<Response>,
554
+ ): PassthroughHandlerDefinition<TParams, TEnv> {
555
+ if (!isPrerenderHandler(prerenderDef)) {
556
+ throw new Error(
557
+ "[rango] Passthrough: first argument must be a Prerender() definition.",
558
+ );
559
+ }
560
+ return {
561
+ __brand: "passthroughHandler" as const,
562
+ prerenderDef,
563
+ liveHandler,
564
+ };
565
+ }
566
+
567
+ /**
568
+ * Type guard to check if a value is a PassthroughHandlerDefinition.
569
+ */
570
+ export function isPassthroughHandler(
571
+ value: unknown,
572
+ ): value is PassthroughHandlerDefinition {
573
+ return (
574
+ typeof value === "object" &&
575
+ value !== null &&
576
+ "__brand" in value &&
577
+ (value as { __brand: unknown }).__brand === "passthroughHandler"
578
+ );
579
+ }