@rangojs/router 0.21.0 → 0.22.1

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 (244) hide show
  1. package/dist/bin/rango.js +13 -1
  2. package/dist/types/browser/event-controller.d.ts +30 -13
  3. package/dist/types/browser/navigation-store.d.ts +9 -0
  4. package/dist/types/browser/partial-update.d.ts +2 -1
  5. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  6. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  7. package/dist/types/browser/react/context.d.ts +14 -0
  8. package/dist/types/browser/rsc-router.d.ts +13 -2
  9. package/dist/types/browser/types.d.ts +10 -0
  10. package/dist/types/build/generate-manifest.d.ts +2 -0
  11. package/dist/types/cache/background-task.d.ts +22 -0
  12. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  13. package/dist/types/cache/cache-policy.d.ts +17 -0
  14. package/dist/types/cache/cache-scope.d.ts +1 -1
  15. package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
  16. package/dist/types/cache/document-cache.d.ts +8 -5
  17. package/dist/types/cache/handle-snapshot.d.ts +23 -0
  18. package/dist/types/cache/index.d.ts +1 -1
  19. package/dist/types/cache/invalidation-order.d.ts +14 -0
  20. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  21. package/dist/types/cache/shell-snapshot.d.ts +10 -1
  22. package/dist/types/cache/store-scope.d.ts +22 -0
  23. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  24. package/dist/types/cache/types.d.ts +63 -14
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
  26. package/dist/types/errors.d.ts +6 -0
  27. package/dist/types/handles/deferred-resolution.d.ts +7 -0
  28. package/dist/types/internal-suspense-audit.d.ts +1 -0
  29. package/dist/types/loader-store.d.ts +4 -8
  30. package/dist/types/outlet-provider.d.ts +2 -1
  31. package/dist/types/prerender/cloudflare.d.ts +49 -0
  32. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  33. package/dist/types/prerender/index.d.ts +12 -0
  34. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  35. package/dist/types/prerender/on-demand.d.ts +272 -0
  36. package/dist/types/prerender/producer-guard.d.ts +33 -0
  37. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  38. package/dist/types/prerender/warm-request.d.ts +65 -0
  39. package/dist/types/prerender/warm.d.ts +65 -0
  40. package/dist/types/prerender/writable-store.d.ts +170 -0
  41. package/dist/types/prerender.d.ts +27 -0
  42. package/dist/types/reverse.d.ts +1 -1
  43. package/dist/types/route-content-wrapper.d.ts +3 -1
  44. package/dist/types/router/error-handling.d.ts +2 -0
  45. package/dist/types/router/handler-context.d.ts +1 -1
  46. package/dist/types/router/match-middleware/cache-lookup.d.ts +14 -0
  47. package/dist/types/router/pattern-matching.d.ts +2 -0
  48. package/dist/types/router/prefetch-default.d.ts +4 -4
  49. package/dist/types/router/prerender-match.d.ts +23 -1
  50. package/dist/types/router/route-trie-builder.d.ts +8 -1
  51. package/dist/types/router/router-context.d.ts +1 -0
  52. package/dist/types/router/router-interfaces.d.ts +30 -0
  53. package/dist/types/router/router-options.d.ts +24 -0
  54. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  55. package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
  56. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  57. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  58. package/dist/types/router/trie-matching.d.ts +2 -0
  59. package/dist/types/rsc/shell-build-manifest.d.ts +6 -4
  60. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  61. package/dist/types/rsc/shell-capture.d.ts +14 -1
  62. package/dist/types/rsc/shell-serve.d.ts +2 -2
  63. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  64. package/dist/types/rsc/types.d.ts +8 -0
  65. package/dist/types/segment-boundary-content.d.ts +17 -0
  66. package/dist/types/segment-loader-promise.d.ts +2 -6
  67. package/dist/types/server/build-version-table.d.ts +9 -0
  68. package/dist/types/server/context.d.ts +12 -4
  69. package/dist/types/server/handle-store.d.ts +2 -0
  70. package/dist/types/server/request-context.d.ts +63 -2
  71. package/dist/types/suspense-audit-react.d.ts +26 -0
  72. package/dist/types/suspense-audit.d.ts +157 -0
  73. package/dist/types/testing/action-form.d.ts +34 -0
  74. package/dist/types/testing/flight.entry.d.ts +2 -0
  75. package/dist/types/testing/index.d.ts +4 -0
  76. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  77. package/dist/types/testing/serve-shell-request.d.ts +34 -4
  78. package/dist/types/testing/shell-status.d.ts +14 -6
  79. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  80. package/dist/types/thenable-status.d.ts +6 -0
  81. package/dist/types/types/handler-context.d.ts +7 -0
  82. package/dist/types/types/route-entry.d.ts +5 -0
  83. package/dist/types/urls/path-helper.d.ts +18 -0
  84. package/dist/types/vite/discovery/state.d.ts +11 -0
  85. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  86. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  87. package/dist/vite/index.js +180 -497
  88. package/package.json +13 -3
  89. package/skills/cache-guide/SKILL.md +18 -17
  90. package/skills/caching/SKILL.md +40 -4
  91. package/skills/catalog.json +1 -1
  92. package/skills/cloudflare/SKILL.md +131 -0
  93. package/skills/comparison/references/framework-comparison.md +1 -1
  94. package/skills/deployment-caching/SKILL.md +33 -12
  95. package/skills/document-cache/SKILL.md +16 -14
  96. package/skills/hooks/SKILL.md +22 -0
  97. package/skills/hooks/handle-and-actions.md +3 -4
  98. package/skills/hooks/navigation.md +10 -0
  99. package/skills/hooks/state.md +5 -4
  100. package/skills/host-router/SKILL.md +18 -0
  101. package/skills/loader/SKILL.md +6 -4
  102. package/skills/middleware/SKILL.md +1 -1
  103. package/skills/migrate-nextjs/SKILL.md +82 -9
  104. package/skills/ppr/SKILL.md +31 -8
  105. package/skills/prerender/SKILL.md +645 -8
  106. package/skills/rango/SKILL.md +1 -0
  107. package/skills/response-routes/SKILL.md +5 -5
  108. package/skills/route/SKILL.md +29 -0
  109. package/skills/testing/SKILL.md +2 -1
  110. package/skills/testing/cache-prerender.md +18 -17
  111. package/skills/testing/serve-shell-request.md +104 -8
  112. package/skills/use-cache/SKILL.md +9 -5
  113. package/skills/vercel/SKILL.md +2 -0
  114. package/skills/view-transitions/SKILL.md +9 -4
  115. package/src/browser/event-controller.ts +69 -25
  116. package/src/browser/link-interceptor.ts +2 -2
  117. package/src/browser/navigation-bridge.ts +75 -35
  118. package/src/browser/navigation-store.ts +26 -0
  119. package/src/browser/navigation-transaction.ts +9 -2
  120. package/src/browser/network-error-handler.ts +2 -0
  121. package/src/browser/partial-update.ts +48 -12
  122. package/src/browser/prefetch/default-strategy.ts +22 -42
  123. package/src/browser/react/Link.tsx +37 -42
  124. package/src/browser/react/NavigationProvider.tsx +63 -3
  125. package/src/browser/react/context.ts +17 -0
  126. package/src/browser/react/location-state.ts +5 -19
  127. package/src/browser/react/use-action.ts +4 -1
  128. package/src/browser/react/use-handle.ts +4 -20
  129. package/src/browser/react/use-link-status.ts +4 -3
  130. package/src/browser/react/use-navigation.ts +4 -1
  131. package/src/browser/react/use-params.ts +4 -1
  132. package/src/browser/react/use-pathname.ts +4 -1
  133. package/src/browser/react/use-search-params.ts +5 -3
  134. package/src/browser/react/use-segments.ts +36 -54
  135. package/src/browser/rsc-router.tsx +79 -39
  136. package/src/browser/segment-reconciler.ts +3 -3
  137. package/src/browser/server-action-bridge.ts +26 -6
  138. package/src/browser/types.ts +11 -0
  139. package/src/build/generate-manifest.ts +17 -0
  140. package/src/build/merge-full-manifests.ts +5 -0
  141. package/src/build/route-trie.ts +1 -0
  142. package/src/cache/background-task.ts +61 -0
  143. package/src/cache/cache-key-utils.ts +91 -12
  144. package/src/cache/cache-policy.ts +11 -2
  145. package/src/cache/cache-runtime.ts +23 -23
  146. package/src/cache/cache-scope.ts +38 -36
  147. package/src/cache/cf/cf-cache-store.ts +16 -11
  148. package/src/cache/document-cache.ts +35 -14
  149. package/src/cache/handle-snapshot.ts +50 -0
  150. package/src/cache/index.ts +1 -0
  151. package/src/cache/invalidation-order.ts +53 -0
  152. package/src/cache/memory-segment-store.ts +52 -1
  153. package/src/cache/shell-snapshot.ts +17 -0
  154. package/src/cache/store-scope.ts +36 -0
  155. package/src/cache/tag-invalidation.ts +7 -5
  156. package/src/cache/types.ts +69 -14
  157. package/src/cache/vercel/vercel-cache-store.ts +20 -6
  158. package/src/client.tsx +40 -3
  159. package/src/errors.ts +13 -6
  160. package/src/handles/deferred-resolution.ts +7 -2
  161. package/src/internal-suspense-audit.ts +12 -0
  162. package/src/loader-store.ts +4 -9
  163. package/src/outlet-provider.tsx +25 -1
  164. package/src/prerender/cloudflare.ts +130 -0
  165. package/src/prerender/create-prerender-trigger.ts +780 -0
  166. package/src/prerender/index.ts +39 -0
  167. package/src/prerender/memory-prerender-store.ts +115 -0
  168. package/src/prerender/on-demand.ts +339 -0
  169. package/src/prerender/producer-guard.ts +66 -0
  170. package/src/prerender/shell-manifest-key.ts +12 -17
  171. package/src/prerender/warm-request.ts +154 -0
  172. package/src/prerender/warm.ts +193 -0
  173. package/src/prerender/writable-store.ts +278 -0
  174. package/src/prerender.ts +29 -0
  175. package/src/reverse.ts +1 -1
  176. package/src/route-content-wrapper.tsx +107 -13
  177. package/src/router/error-handling.ts +12 -12
  178. package/src/router/find-match.ts +1 -0
  179. package/src/router/handler-context.ts +2 -0
  180. package/src/router/intercept-resolution.ts +15 -8
  181. package/src/router/loader-resolution.ts +2 -10
  182. package/src/router/match-middleware/background-revalidation.ts +2 -0
  183. package/src/router/match-middleware/cache-lookup.ts +230 -38
  184. package/src/router/pattern-matching.ts +14 -0
  185. package/src/router/prefetch-default.ts +4 -4
  186. package/src/router/prerender-match.ts +460 -352
  187. package/src/router/route-trie-builder.ts +9 -0
  188. package/src/router/router-context.ts +1 -0
  189. package/src/router/router-interfaces.ts +33 -0
  190. package/src/router/router-options.ts +30 -0
  191. package/src/router/segment-resolution/fresh.ts +20 -19
  192. package/src/router/segment-resolution/loader-cache.ts +15 -17
  193. package/src/router/segment-resolution/revalidation.ts +34 -24
  194. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  195. package/src/router/trie-matching.ts +3 -0
  196. package/src/router.ts +107 -1
  197. package/src/rsc/handler.ts +51 -4
  198. package/src/rsc/progressive-enhancement.ts +5 -1
  199. package/src/rsc/response-cache-serve.ts +10 -4
  200. package/src/rsc/rsc-rendering.ts +53 -11
  201. package/src/rsc/shell-build-manifest.ts +10 -8
  202. package/src/rsc/shell-capture-constants.ts +39 -7
  203. package/src/rsc/shell-capture.ts +108 -19
  204. package/src/rsc/shell-serve.ts +12 -6
  205. package/src/rsc/ssr-module-loader.ts +26 -0
  206. package/src/rsc/types.ts +8 -0
  207. package/src/segment-boundary-content.ts +25 -0
  208. package/src/segment-loader-promise.ts +28 -38
  209. package/src/segment-system.tsx +117 -57
  210. package/src/server/build-version-table.ts +15 -0
  211. package/src/server/context.ts +40 -17
  212. package/src/server/cookie-store.ts +5 -0
  213. package/src/server/handle-store.ts +13 -0
  214. package/src/server/loader-registry.ts +6 -1
  215. package/src/server/request-context.ts +87 -1
  216. package/src/ssr/ssr-root.tsx +20 -13
  217. package/src/suspense-audit-react.tsx +98 -0
  218. package/src/suspense-audit.ts +1030 -0
  219. package/src/testing/action-form.ts +66 -0
  220. package/src/testing/flight-runtime.d.ts +27 -0
  221. package/src/testing/flight.entry.ts +3 -0
  222. package/src/testing/index.ts +19 -0
  223. package/src/testing/internal/server-modules.ts +32 -0
  224. package/src/testing/render-route.tsx +25 -12
  225. package/src/testing/serve-shell-request.ts +190 -34
  226. package/src/testing/shell-status.ts +17 -6
  227. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  228. package/src/thenable-status.ts +10 -0
  229. package/src/types/handler-context.ts +7 -0
  230. package/src/types/route-entry.ts +6 -0
  231. package/src/urls/path-helper.ts +69 -9
  232. package/src/use-loader.tsx +57 -30
  233. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  234. package/src/vite/discovery/discover-routers.ts +15 -0
  235. package/src/vite/discovery/shell-prerender-phase.ts +5 -5
  236. package/src/vite/discovery/state.ts +17 -1
  237. package/src/vite/inject-client-debug.ts +42 -20
  238. package/src/vite/plugins/virtual-entries.ts +8 -1
  239. package/src/vite/router-discovery.ts +14 -0
  240. package/src/vite/utils/bundle-analysis.ts +39 -7
  241. package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
  242. package/dist/types/segment-content-promise.d.ts +0 -13
  243. package/src/browser/react/subscribe-to-nothing.ts +0 -7
  244. package/src/segment-content-promise.ts +0 -67
@@ -2463,6 +2463,7 @@ function createDiscoveryState(entryPath, opts) {
2463
2463
  buildData: [],
2464
2464
  handlerChunkInfoMap: /* @__PURE__ */ new Map(),
2465
2465
  staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
2466
+ onDemandHandlerIds: /* @__PURE__ */ new Map(),
2466
2467
  rscEntryFileName: null,
2467
2468
  resolvedPrerenderModules: void 0,
2468
2469
  resolvedStaticModules: void 0,
@@ -4457,6 +4458,12 @@ async function initializeApp() {
4457
4458
  // is the default; createRouter({ strictMode: false }) ships the opt-out in the
4458
4459
  // payload metadata. StrictMode emits no DOM, so toggling never changes markup.
4459
4460
  const { strictMode, initialPayload } = await initBrowserApp({ rscStream, deps });
4461
+ // Set only on a document a no-JS useActionState POST rendered: React needs
4462
+ // the server's form state to hydrate the hook without resetting it. A state
4463
+ // Flight could not carry rejects: hydrate without it, as before.
4464
+ const formState = await Promise.resolve(initialPayload.formState).catch(
4465
+ () => undefined
4466
+ );
4460
4467
 
4461
4468
  if (import.meta.hot) {
4462
4469
  const { startDevDiscoveryHandshake } = await import(
@@ -4471,7 +4478,8 @@ async function initializeApp() {
4471
4478
  const app = createElement(Rango);
4472
4479
  hydrateRoot(
4473
4480
  document,
4474
- strictMode === false ? app : createElement(StrictMode, null, app)
4481
+ strictMode === false ? app : createElement(StrictMode, null, app),
4482
+ formState ? { formState } : undefined
4475
4483
  );
4476
4484
  }
4477
4485
 
@@ -5420,7 +5428,7 @@ import { resolve } from "node:path";
5420
5428
  // package.json
5421
5429
  var package_default = {
5422
5430
  name: "@rangojs/router",
5423
- version: "0.21.0",
5431
+ version: "0.22.1",
5424
5432
  description: "Django-inspired RSC router with composable URL patterns",
5425
5433
  keywords: [
5426
5434
  "react",
@@ -5545,6 +5553,16 @@ var package_default = {
5545
5553
  "react-server": "./src/cloudflare/index.ts",
5546
5554
  default: "./src/cloudflare/index.ts"
5547
5555
  },
5556
+ "./prerender": {
5557
+ types: "./dist/types/prerender/index.d.ts",
5558
+ "react-server": "./src/prerender/index.ts",
5559
+ default: "./src/prerender/index.ts"
5560
+ },
5561
+ "./prerender/cloudflare": {
5562
+ types: "./dist/types/prerender/cloudflare.d.ts",
5563
+ "react-server": "./src/prerender/cloudflare.ts",
5564
+ default: "./src/prerender/cloudflare.ts"
5565
+ },
5548
5566
  "./vercel": {
5549
5567
  types: "./dist/types/vercel/index.d.ts",
5550
5568
  "react-server": "./src/vercel/index.ts",
@@ -5618,7 +5636,7 @@ var package_default = {
5618
5636
  },
5619
5637
  dependencies: {
5620
5638
  "@types/debug": "^4.1.12",
5621
- "@vitejs/plugin-rsc": "^0.5.35",
5639
+ "@vitejs/plugin-rsc": "^0.5.36",
5622
5640
  debug: "^4.4.1",
5623
5641
  "magic-string": "^0.30.17",
5624
5642
  picomatch: "^4.0.4",
@@ -5643,7 +5661,7 @@ var package_default = {
5643
5661
  jiti: "^2.7.0",
5644
5662
  react: "catalog:",
5645
5663
  "react-dom": "catalog:",
5646
- vitest: "^4.1.9"
5664
+ vitest: "^4.1.11"
5647
5665
  },
5648
5666
  peerDependencies: {
5649
5667
  "@cloudflare/vite-plugin": "^1.42.1",
@@ -7680,6 +7698,11 @@ import React from "react";
7680
7698
  // src/server/context.ts
7681
7699
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
7682
7700
 
7701
+ // src/cache/cache-key-utils.ts
7702
+ function routerKeyPrefix(routerId2) {
7703
+ return `${encodeURIComponent(routerId2)}@`;
7704
+ }
7705
+
7683
7706
  // src/rsc/shell-capture-constants.ts
7684
7707
  var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
7685
7708
  var PPR_LANE_HINT = 'Lane rule: only loader(Def, { ssr: false }) executes at capture and bakes its non-promise data; every other loader is live (masked at capture, fresh per request) and needs loading() or an inline <Suspense> above its reader. See the /ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
@@ -7743,15 +7766,28 @@ function normalizeCaptureTimeout(value) {
7743
7766
  var SHELL_PRELUDE_CHUNK_BYTES = 32 * 1024;
7744
7767
 
7745
7768
  // src/vite/inject-client-debug.ts
7746
- function isRouterInternalDebugId(id) {
7747
- if (!id.includes("internal-debug")) return false;
7769
+ var FLAG_MODULE = /\/(internal-debug|internal-suspense-audit)\.[cm]?[jt]sx?(\?|$)/;
7770
+ var FLAG_NAMES = {
7771
+ "internal-debug": "INTERNAL_RANGO_DEBUG",
7772
+ "internal-suspense-audit": "INTERNAL_RANGO_SUSPENSE_AUDIT"
7773
+ };
7774
+ function routerFlagName(id) {
7775
+ if (!id.includes("internal-")) return void 0;
7748
7776
  const norm = id.replace(/\\/g, "/");
7749
- return /\/internal-debug\.[cm]?[jt]sx?(\?|$)/.test(norm) && (norm.includes("/@rangojs/router/") || norm.includes("/packages/rangojs-router/"));
7777
+ if (!norm.includes("/@rangojs/router/") && !norm.includes("/packages/rangojs-router/")) {
7778
+ return void 0;
7779
+ }
7780
+ const match = FLAG_MODULE.exec(norm);
7781
+ return match ? FLAG_NAMES[match[1]] : void 0;
7782
+ }
7783
+ function isRouterInternalDebugId(id) {
7784
+ return routerFlagName(id) !== void 0;
7750
7785
  }
7751
7786
  function injectClientDebugFlag(id) {
7752
- if (!isRouterInternalDebugId(id)) return null;
7787
+ const name = routerFlagName(id);
7788
+ if (name === void 0) return null;
7753
7789
  return {
7754
- code: `export const INTERNAL_RANGO_DEBUG = ${!!process.env.INTERNAL_RANGO_DEBUG};
7790
+ code: `export const ${name} = ${!!process.env[name]};
7755
7791
  `,
7756
7792
  map: null
7757
7793
  };
@@ -7964,8 +8000,9 @@ function findMatchingParenInBundle(code, openParenPos) {
7964
8000
  }
7965
8001
  return depth === 0 ? pos : -1;
7966
8002
  }
7967
- function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detectPassthrough) {
8003
+ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detectPassthrough, onDemandHandlerIds) {
7968
8004
  const handlers = [];
8005
+ const scanPassthrough = detectPassthrough && chunkCode.includes("passthrough");
7969
8006
  for (const [, handlerNames] of handlerModules) {
7970
8007
  for (const name of handlerNames) {
7971
8008
  const eName = escapeRegExp(name);
@@ -7975,7 +8012,7 @@ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detec
7975
8012
  const match = chunkCode.match(idPattern);
7976
8013
  if (!match) continue;
7977
8014
  let isPassthrough = false;
7978
- if (detectPassthrough) {
8015
+ if (scanPassthrough) {
7979
8016
  const eFnName = escapeRegExp(fnName);
7980
8017
  const callStartRe = new RegExp(
7981
8018
  `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
@@ -7990,7 +8027,12 @@ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detec
7990
8027
  }
7991
8028
  }
7992
8029
  }
7993
- handlers.push({ name, handlerId: match[1], passthrough: isPassthrough });
8030
+ handlers.push({
8031
+ name,
8032
+ handlerId: match[1],
8033
+ passthrough: isPassthrough,
8034
+ onDemand: onDemandHandlerIds?.has(match[1]) ?? false
8035
+ });
7994
8036
  }
7995
8037
  }
7996
8038
  return handlers;
@@ -7999,8 +8041,8 @@ function evictHandlerCode(code, exports, fnName, brand) {
7999
8041
  const originalSize = Buffer.byteLength(code);
8000
8042
  let modified = code;
8001
8043
  const eFnName = escapeRegExp(fnName);
8002
- for (const { name, handlerId, passthrough } of exports) {
8003
- if (passthrough) continue;
8044
+ for (const { name, handlerId, passthrough, onDemand } of exports) {
8045
+ if (passthrough || onDemand) continue;
8004
8046
  const eName = escapeRegExp(name);
8005
8047
  const callStartRe = new RegExp(
8006
8048
  `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
@@ -8121,7 +8163,7 @@ function parsePattern(pattern) {
8121
8163
  }
8122
8164
 
8123
8165
  // src/router/route-trie-builder.ts
8124
- function buildRouteTrie(routeManifest, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes) {
8166
+ function buildRouteTrie(routeManifest, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes, onDemandRouteNames) {
8125
8167
  const root = {};
8126
8168
  for (const [routeName, pattern] of Object.entries(routeManifest)) {
8127
8169
  const staticPrefix = routeToStaticPrefix[routeName] || "";
@@ -8136,6 +8178,7 @@ function buildRouteTrie(routeManifest, routeToStaticPrefix, routeTrailingSlash,
8136
8178
  ...trailingSlash ? { ts: trailingSlash } : {},
8137
8179
  ...prerenderRouteNames?.has(routeName) ? { pr: true } : {},
8138
8180
  ...passthroughRouteNames?.has(routeName) ? { pt: true } : {},
8181
+ ...onDemandRouteNames?.has(routeName) ? { od: true } : {},
8139
8182
  ...responseType ? { rt: responseType } : {}
8140
8183
  });
8141
8184
  }
@@ -8328,7 +8371,8 @@ function buildPerRouterTrie(manifest) {
8328
8371
  manifest.routeTrailingSlash,
8329
8372
  manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0,
8330
8373
  manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0,
8331
- manifest.responseTypeRoutes
8374
+ manifest.responseTypeRoutes,
8375
+ manifest.onDemandRoutes ? new Set(manifest.onDemandRoutes) : void 0
8332
8376
  );
8333
8377
  }
8334
8378
 
@@ -8482,6 +8526,11 @@ function mergeFullManifests(manifests) {
8482
8526
  manifest.passthroughRoutes,
8483
8527
  overwrittenRoutes
8484
8528
  ),
8529
+ onDemandRoutes: mergeRouteList(
8530
+ merged.onDemandRoutes,
8531
+ manifest.onDemandRoutes,
8532
+ overwrittenRoutes
8533
+ ),
8485
8534
  responseTypeRoutes: mergeRouteRecord(
8486
8535
  merged.responseTypeRoutes,
8487
8536
  manifest.responseTypeRoutes,
@@ -9268,9 +9317,10 @@ function isUnderTestRunner() {
9268
9317
  // src/client.tsx
9269
9318
  import {
9270
9319
  Component as Component3,
9271
- createElement as createElement3,
9320
+ createElement as createElement4,
9321
+ isValidElement as isValidElement2,
9272
9322
  useContext as useContext17,
9273
- useMemo as useMemo8,
9323
+ useMemo as useMemo9,
9274
9324
  Suspense as Suspense2
9275
9325
  } from "react";
9276
9326
 
@@ -9281,7 +9331,6 @@ import {
9281
9331
  useEffect,
9282
9332
  useRef,
9283
9333
  useOptimistic,
9284
- useSyncExternalStore,
9285
9334
  startTransition
9286
9335
  } from "react";
9287
9336
 
@@ -9359,10 +9408,7 @@ function createHandle(collect, __injectedId) {
9359
9408
  import { createContext } from "react";
9360
9409
  var NavigationStoreContext = createContext(null);
9361
9410
  var LocationStateContext = createContext(void 0);
9362
-
9363
- // src/browser/react/subscribe-to-nothing.ts
9364
- var subscribeToNothing = () => () => {
9365
- };
9411
+ var HydrationBarrierContext = createContext(0);
9366
9412
 
9367
9413
  // src/handles/meta.ts
9368
9414
  function isUnsetDescriptor(descriptor) {
@@ -9568,17 +9614,31 @@ import { createContext as createContext4 } from "react";
9568
9614
  var OutletContext = createContext4(null);
9569
9615
 
9570
9616
  // src/route-content-wrapper.tsx
9571
- import { Component, Suspense, use } from "react";
9617
+ import { Component, Suspense, use, useMemo as useMemo3 } from "react";
9618
+
9619
+ // src/outlet-provider.tsx
9620
+ import {
9621
+ useContext as useContext4,
9622
+ useMemo
9623
+ } from "react";
9624
+
9625
+ // src/suspense-audit-react.tsx
9626
+ import { useEffect as useEffect3, useLayoutEffect, useRef as useRef2 } from "react";
9627
+
9628
+ // src/suspense-audit.ts
9629
+ init_internal_debug();
9630
+
9631
+ // src/internal-suspense-audit.ts
9632
+ var INTERNAL_RANGO_SUSPENSE_AUDIT = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_SUSPENSE_AUDIT);
9572
9633
 
9573
9634
  // src/outlet-provider.tsx
9574
- import { useContext as useContext4, useMemo } from "react";
9575
9635
  import { jsx as jsx3 } from "react/jsx-runtime";
9576
9636
 
9577
9637
  // src/decode-loader-results.ts
9578
9638
  import { createElement as createElement2 } from "react";
9579
9639
 
9580
9640
  // src/loader-redirect.tsx
9581
- import { useEffect as useEffect3 } from "react";
9641
+ import { useEffect as useEffect4 } from "react";
9582
9642
 
9583
9643
  // src/browser/react/use-router.ts
9584
9644
  import { useContext as useContext6, useMemo as useMemo2 } from "react";
@@ -9608,6 +9668,7 @@ init_redirect_origin();
9608
9668
 
9609
9669
  // src/route-content-wrapper.tsx
9610
9670
  import { jsx as jsx4 } from "react/jsx-runtime";
9671
+ import { createElement as createElement3 } from "react";
9611
9672
 
9612
9673
  // src/use-loader.tsx
9613
9674
  import {
@@ -9616,18 +9677,18 @@ import {
9616
9677
  use as use2,
9617
9678
  useCallback,
9618
9679
  useContext as useContext7,
9619
- useEffect as useEffect4,
9620
- useMemo as useMemo3,
9680
+ useEffect as useEffect5,
9681
+ useMemo as useMemo4,
9621
9682
  useOptimistic as useOptimistic2,
9622
- useRef as useRef2,
9683
+ useRef as useRef3,
9623
9684
  useState as useState3
9624
9685
  } from "react";
9625
9686
 
9626
9687
  // src/thenable-status.ts
9627
- function unwrapsSynchronously(stream) {
9628
- const status = stream.status;
9629
- return status === "fulfilled" || status === "resolved_model" || status === "resolved_module";
9630
- }
9688
+ var SETTLED_THENABLE = Object.assign(
9689
+ Promise.resolve(void 0),
9690
+ { status: "fulfilled", value: void 0, _debugInfo: [] }
9691
+ );
9631
9692
 
9632
9693
  // src/loader-store.ts
9633
9694
  var EMPTY_SNAPSHOT = Object.freeze({
@@ -9637,410 +9698,19 @@ var EMPTY_SNAPSHOT = Object.freeze({
9637
9698
  isLoading: false,
9638
9699
  requestId: 0
9639
9700
  });
9640
- function normalizeGroups(group) {
9641
- if (group === void 0) return [];
9642
- if (typeof group === "string") return [group];
9643
- return [...new Set(group)];
9644
- }
9645
- var LoaderStore = class {
9646
- constructor() {
9647
- this.entries = /* @__PURE__ */ new Map();
9648
- /** loader.$$id -> set of bucket keys, so clearFamily() can reach every bucket. */
9649
- this.families = /* @__PURE__ */ new Map();
9650
- /** refresh group name -> set of bucket keys, for refreshGroups(). */
9651
- this.groups = /* @__PURE__ */ new Map();
9652
- /**
9653
- * loader.$$id -> per-loader streams a committed navigation is still
9654
- * receiving. Registered and announced by announcePendingStreams(), which
9655
- * browser/partial-update.ts calls INSIDE every transition commit, so a
9656
- * reader whose content is held on screen by that transition pins
9657
- * `isLoading: true` for the data it is still showing until the transition
9658
- * lands (use-loader.tsx). Family-keyed on purpose: a persisting layout
9659
- * reader of the same createLoader the child is re-running is pinned too
9660
- * (the loader is in flight; not "this segment's copy is being replaced" —
9661
- * issue #862, working as designed). The new tree's own read of the loader
9662
- * suspends at the read site and never observes the flag. A Set, not a
9663
- * flag: overlapping navigations (second click before the first stream
9664
- * lands) keep the family pending until the LAST stream settles.
9665
- */
9666
- this.pendingStreams = /* @__PURE__ */ new Map();
9667
- }
9668
- getOrCreate(bucketKey) {
9669
- let e = this.entries.get(bucketKey);
9670
- if (!e) {
9671
- e = {
9672
- snapshot: EMPTY_SNAPSHOT,
9673
- listeners: /* @__PURE__ */ new Set(),
9674
- latestRequestId: 0,
9675
- loaderId: bucketKey,
9676
- sticky: false,
9677
- pendingClear: false,
9678
- clearWhenSettled: false,
9679
- groups: /* @__PURE__ */ new Map(),
9680
- refetch: void 0,
9681
- streamPendingListeners: /* @__PURE__ */ new Set()
9682
- };
9683
- this.entries.set(bucketKey, e);
9684
- }
9685
- return e;
9686
- }
9687
- registerFamily(loaderId, bucketKey) {
9688
- let fam = this.families.get(loaderId);
9689
- if (!fam) {
9690
- fam = /* @__PURE__ */ new Set();
9691
- this.families.set(loaderId, fam);
9692
- }
9693
- fam.add(bucketKey);
9694
- }
9695
- /**
9696
- * Subscribe to entry changes for `bucketKey`.
9697
- * Returns an unsubscribe function. A sticky bucket's entry is kept around even
9698
- * after the last subscriber leaves so that an in-flight `load()` can still
9699
- * commit if the consumer remounts. An ephemeral bucket is dropped once its
9700
- * last subscriber leaves (deferred, see maybeScheduleRefcountClear).
9701
- */
9702
- subscribe(bucketKey, cb, options) {
9703
- const e = this.getOrCreate(bucketKey);
9704
- const loaderId = options?.loaderId ?? bucketKey;
9705
- e.loaderId = loaderId;
9706
- this.registerFamily(loaderId, bucketKey);
9707
- if (options?.ephemeral !== true) e.sticky = true;
9708
- const groups = normalizeGroups(options?.group);
9709
- for (const group of groups) {
9710
- this.addToGroup(group, bucketKey, e, options?.refetch);
9711
- }
9712
- e.pendingClear = false;
9713
- e.clearWhenSettled = false;
9714
- e.listeners.add(cb);
9715
- const onStreamPending = options?.onStreamPending;
9716
- if (onStreamPending) e.streamPendingListeners.add(onStreamPending);
9717
- return () => {
9718
- e.listeners.delete(cb);
9719
- if (onStreamPending) e.streamPendingListeners.delete(onStreamPending);
9720
- for (const group of groups) this.releaseGroup(group, bucketKey, e);
9721
- this.maybeScheduleRefcountClear(bucketKey, e);
9722
- };
9723
- }
9724
- addToGroup(group, bucketKey, e, refetch) {
9725
- e.groups.set(group, (e.groups.get(group) ?? 0) + 1);
9726
- if (refetch) e.refetch = refetch;
9727
- let members = this.groups.get(group);
9728
- if (!members) {
9729
- members = /* @__PURE__ */ new Set();
9730
- this.groups.set(group, members);
9731
- }
9732
- members.add(bucketKey);
9733
- }
9734
- releaseGroup(group, bucketKey, e) {
9735
- const next = (e.groups.get(group) ?? 0) - 1;
9736
- if (next > 0) {
9737
- e.groups.set(group, next);
9738
- return;
9739
- }
9740
- e.groups.delete(group);
9741
- const members = this.groups.get(group);
9742
- if (members) {
9743
- members.delete(bucketKey);
9744
- if (members.size === 0) this.groups.delete(group);
9745
- }
9746
- }
9747
- /** Remove a bucket from every group it belongs to (used when it is dropped). */
9748
- removeFromAllGroups(bucketKey, e) {
9749
- for (const group of e.groups.keys()) {
9750
- const members = this.groups.get(group);
9751
- if (members) {
9752
- members.delete(bucketKey);
9753
- if (members.size === 0) this.groups.delete(group);
9754
- }
9755
- }
9756
- e.groups.clear();
9757
- }
9758
- /**
9759
- * Refresh every currently-mounted bucket tagged with ANY of the given group
9760
- * names, with a plain GET (no params/body). Accepts a single name or an array
9761
- * of names. A bucket that belongs to more than one of the named groups is
9762
- * refreshed exactly once (members are unioned and deduped by bucket key), as
9763
- * are multiple reads of one bucket. Resolves when all refreshes settle; rejects
9764
- * with an `AggregateError` of the failures if any member fails — each failing
9765
- * member also records its error on its own snapshot.
9766
- */
9767
- async refreshGroups(groups) {
9768
- const names = typeof groups === "string" ? [groups] : groups;
9769
- const buckets = /* @__PURE__ */ new Set();
9770
- for (const name of names) {
9771
- const members = this.groups.get(name);
9772
- if (!members) continue;
9773
- for (const bucketKey of members) buckets.add(bucketKey);
9774
- }
9775
- if (buckets.size === 0) return;
9776
- const thunks = [];
9777
- for (const bucketKey of buckets) {
9778
- const e = this.entries.get(bucketKey);
9779
- if (!e || e.listeners.size === 0 || !e.refetch) continue;
9780
- thunks.push(e.refetch);
9781
- }
9782
- if (thunks.length === 0) return;
9783
- const results = await Promise.allSettled(thunks.map((t) => t()));
9784
- const reasons = results.filter((r) => r.status === "rejected").map((r) => r.reason);
9785
- if (reasons.length > 0) {
9786
- const label = names.map((n) => `"${n}"`).join(", ");
9787
- throw new AggregateError(
9788
- reasons,
9789
- `refreshGroups(${label}) had ${reasons.length} failure(s)`
9790
- );
9791
- }
9792
- }
9793
- /**
9794
- * Schedule a deferred drop for an ad-hoc (never-sticky) bucket whose last
9795
- * subscriber just left. Deferred to a microtask so a StrictMode / transition
9796
- * remount that resubscribes in the same tick cancels it.
9797
- */
9798
- maybeScheduleRefcountClear(bucketKey, e) {
9799
- if (e.listeners.size > 0) return;
9800
- if (e.sticky) return;
9801
- if (e.pendingClear) return;
9802
- e.pendingClear = true;
9803
- queueMicrotask(() => {
9804
- if (!e.pendingClear) return;
9805
- e.pendingClear = false;
9806
- if (e.listeners.size > 0) return;
9807
- if (e.snapshot.isLoading) {
9808
- e.clearWhenSettled = true;
9809
- return;
9810
- }
9811
- this.dropBucket(bucketKey);
9812
- });
9813
- }
9814
- /** Remove a bucket entry entirely and prune it from its family/group indexes. */
9815
- dropBucket(bucketKey) {
9816
- const e = this.entries.get(bucketKey);
9817
- if (!e) return;
9818
- this.removeFromAllGroups(bucketKey, e);
9819
- this.entries.delete(bucketKey);
9820
- const fam = this.families.get(e.loaderId);
9821
- if (fam) {
9822
- fam.delete(bucketKey);
9823
- if (fam.size === 0) this.families.delete(e.loaderId);
9824
- }
9825
- }
9826
- /** Drop an ephemeral bucket that settled with no subscribers left. */
9827
- dropIfSettled(bucketKey, e) {
9828
- if (e.clearWhenSettled && e.listeners.size === 0) {
9829
- e.clearWhenSettled = false;
9830
- this.dropBucket(bucketKey);
9831
- }
9832
- }
9833
- /**
9834
- * Returns the current snapshot for `bucketKey`. Stable reference between
9835
- * mutations — subscribers rely on this to avoid spurious re-renders.
9836
- * Returns `EMPTY_SNAPSHOT` (a singleton) when the entry has never been
9837
- * mutated or has been cleared.
9838
- */
9839
- getSnapshot(bucketKey) {
9840
- return this.entries.get(bucketKey)?.snapshot ?? EMPTY_SNAPSHOT;
9841
- }
9842
- /**
9843
- * Reserve a fresh request id for an upcoming `load()` call. The returned id
9844
- * is the new "latest"; any older in-flight requests will fail their gating
9845
- * check on `finishData` / `finishError` / `finishLoading` and be dropped.
9846
- *
9847
- * Callers should follow with `beginRequest(id, requestId)` to flip the
9848
- * loading flag on AND clear any leftover error from a previous attempt
9849
- * — the latter matters for `throwOnError: false` consumers, which would
9850
- * otherwise keep showing the stale error throughout the retry.
9851
- */
9852
- reserveRequestId(bucketKey) {
9853
- const e = this.getOrCreate(bucketKey);
9854
- e.latestRequestId++;
9855
- return e.latestRequestId;
9856
- }
9857
- /**
9858
- * Mark the request as in-flight: `isLoading = true`, `error = null`.
9859
- * Combines the two operations so a retry doesn't render the previous
9860
- * error during the new request. Gated on `requestId === latestRequestId`
9861
- * for symmetry with the other mutators.
9862
- */
9863
- beginRequest(bucketKey, requestId) {
9864
- const e = this.entries.get(bucketKey);
9865
- if (!e || requestId !== e.latestRequestId) return;
9866
- if (e.snapshot.isLoading && e.snapshot.error === null) return;
9867
- e.snapshot = Object.freeze({
9868
- value: e.snapshot.value,
9869
- hasValue: e.snapshot.hasValue,
9870
- error: null,
9871
- isLoading: true,
9872
- requestId
9873
- });
9874
- this.notify(e);
9875
- }
9876
- /**
9877
- * Commit a successful result. No-op if `requestId` is not the latest
9878
- * (a newer `load()` was issued or `clear()` ran). Clearing `error` is
9879
- * intentional: a successful refetch should hide the previous failure.
9880
- */
9881
- finishData(bucketKey, requestId, value) {
9882
- const e = this.entries.get(bucketKey);
9883
- if (!e || requestId !== e.latestRequestId) return;
9884
- e.snapshot = Object.freeze({
9885
- value,
9886
- hasValue: true,
9887
- error: null,
9888
- isLoading: false,
9889
- requestId
9890
- });
9891
- this.notify(e);
9892
- this.dropIfSettled(bucketKey, e);
9893
- }
9894
- /**
9895
- * Commit an error. Preserves the last good `value` so consumers can keep
9896
- * showing previous data while displaying the error if they choose. No-op
9897
- * if `requestId` is not the latest.
9898
- */
9899
- finishError(bucketKey, requestId, error) {
9900
- const e = this.entries.get(bucketKey);
9901
- if (!e || requestId !== e.latestRequestId) return;
9902
- e.snapshot = Object.freeze({
9903
- value: e.snapshot.value,
9904
- hasValue: e.snapshot.hasValue,
9905
- error,
9906
- isLoading: false,
9907
- requestId
9908
- });
9909
- this.notify(e);
9910
- this.dropIfSettled(bucketKey, e);
9911
- }
9912
- /**
9913
- * Update loading flag. Gated on `requestId` to fix the race where an old
9914
- * load() finishes after a new one started — its `setLoading(false)` would
9915
- * otherwise hide the new request's spinner.
9916
- */
9917
- setLoading(bucketKey, requestId, isLoading) {
9918
- const e = this.entries.get(bucketKey);
9919
- if (!e || requestId !== e.latestRequestId) return;
9920
- if (e.snapshot.isLoading === isLoading) return;
9921
- e.snapshot = Object.freeze({
9922
- ...e.snapshot,
9923
- isLoading
9924
- });
9925
- this.notify(e);
9926
- }
9927
- /**
9928
- * Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
9929
- * `load()` whose promise is still pending will fail its gate when it resolves
9930
- * and be dropped — prevents pre-navigation loads from clobbering the new
9931
- * route's context. The entry itself is kept (sticky-bucket semantics).
9932
- */
9933
- clear(bucketKey) {
9934
- const e = this.entries.get(bucketKey);
9935
- if (!e) return;
9936
- e.latestRequestId++;
9937
- if (e.snapshot === EMPTY_SNAPSHOT) return;
9938
- e.snapshot = EMPTY_SNAPSHOT;
9939
- this.notify(e);
9940
- }
9941
- /**
9942
- * Reset every sticky bucket belonging to `loaderId`. Called on navigation /
9943
- * route-context change: all route-registered reads of the loader (keyed or
9944
- * not) drop back to seeding from fresh `loaderData`. Ephemeral buckets are
9945
- * intentionally left alone — they are governed by subscriber refcount, so a
9946
- * persistent keyed reader outside the outlet keeps its value across a
9947
- * navigation rather than blanking out.
9948
- */
9949
- clearFamily(loaderId) {
9950
- const fam = this.families.get(loaderId);
9951
- if (!fam) return;
9952
- for (const bucketKey of fam) {
9953
- const e = this.entries.get(bucketKey);
9954
- if (!e || !e.sticky) continue;
9955
- this.clear(bucketKey);
9956
- }
9957
- }
9958
- notify(e) {
9959
- for (const cb of e.listeners) cb();
9960
- }
9961
- /**
9962
- * Register a still-streaming per-loader result for `loaderId`. No-op for a
9963
- * settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
9964
- * and action lanes commit settled chunks, and a { ssr: false } loader is
9965
- * awaited before flush. Released as the stream settles, either way.
9966
- */
9967
- trackPendingStream(loaderId, stream) {
9968
- if (unwrapsSynchronously(stream) || stream.status === "rejected") {
9969
- return;
9970
- }
9971
- let set = this.pendingStreams.get(loaderId);
9972
- if (set?.has(stream)) return;
9973
- if (!set) {
9974
- set = /* @__PURE__ */ new Set();
9975
- this.pendingStreams.set(loaderId, set);
9976
- }
9977
- set.add(stream);
9978
- const release = () => {
9979
- const current = this.pendingStreams.get(loaderId);
9980
- if (!current || !current.delete(stream)) return;
9981
- if (current.size === 0) this.pendingStreams.delete(loaderId);
9982
- };
9983
- stream.then(release, release);
9984
- }
9985
- /** True while any navigation stream for `loaderId` is still pending. */
9986
- isStreamPending(loaderId) {
9987
- return this.pendingStreams.has(loaderId);
9988
- }
9989
- /**
9990
- * Register the loader segments of a tree being committed whose data is
9991
- * still a pending promise, then fire `onStreamPending` on every subscriber
9992
- * of every family with a pending stream. MUST run inside the startTransition
9993
- * that commits the tree: the hook answers with a useOptimistic update, which
9994
- * React reverts exactly when that transition commits — so the held reader
9995
- * reports isLoading:true for as long as its data stays on screen and not a
9996
- * frame longer. Outside a transition React reverts an optimistic update on
9997
- * the next render, pinning nothing useful. Registering here rather than in
9998
- * the tree build keeps register + announce in one call; settled chunks
9999
- * (cached/reused segments, forceAwait lanes) are skipped by
10000
- * trackPendingStream.
10001
- */
10002
- announcePendingStreams(segments) {
10003
- for (const segment of segments) {
10004
- if (segment.type === "loader" && segment.loaderId && segment.loaderData instanceof Promise) {
10005
- this.trackPendingStream(segment.loaderId, segment.loaderData);
10006
- }
10007
- }
10008
- for (const loaderId of this.pendingStreams.keys()) {
10009
- const fam = this.families.get(loaderId);
10010
- if (!fam) continue;
10011
- for (const bucketKey of fam) {
10012
- const e = this.entries.get(bucketKey);
10013
- if (!e) continue;
10014
- for (const cb of e.streamPendingListeners) cb();
10015
- }
10016
- }
10017
- }
10018
- /**
10019
- * Test-only escape hatch. Drops every entry. Production code should never
10020
- * call this; the store is process-scoped and lives for the tab's lifetime.
10021
- * @internal
10022
- */
10023
- reset() {
10024
- this.entries.clear();
10025
- this.families.clear();
10026
- this.groups.clear();
10027
- this.pendingStreams.clear();
10028
- }
10029
- };
10030
- var loaderStore = new LoaderStore();
10031
9701
 
10032
9702
  // src/browser/react/use-navigation.ts
10033
9703
  import {
10034
9704
  useContext as useContext8,
10035
9705
  useState as useState4,
10036
- useEffect as useEffect5,
9706
+ useEffect as useEffect6,
10037
9707
  useOptimistic as useOptimistic3,
10038
9708
  startTransition as startTransition3,
10039
- useRef as useRef3
9709
+ useRef as useRef4
10040
9710
  } from "react";
10041
9711
 
10042
9712
  // src/browser/react/use-pathname.ts
10043
- import { useContext as useContext9, useState as useState5, useEffect as useEffect6, useRef as useRef4 } from "react";
9713
+ import { useContext as useContext9, useState as useState5, useEffect as useEffect7, useRef as useRef5 } from "react";
10044
9714
 
10045
9715
  // src/client-urls/optimistic-location.ts
10046
9716
  import { createContext as createContext6 } from "react";
@@ -10050,38 +9720,39 @@ var OptimisticLocationContext = createContext6(null);
10050
9720
  import {
10051
9721
  useCallback as useCallback2,
10052
9722
  useContext as useContext10,
10053
- useEffect as useEffect7,
10054
- useMemo as useMemo4,
10055
- useRef as useRef5,
9723
+ useEffect as useEffect8,
9724
+ useMemo as useMemo5,
9725
+ useRef as useRef6,
10056
9726
  useState as useState6
10057
9727
  } from "react";
10058
9728
 
10059
9729
  // src/browser/react/use-params.ts
10060
- import { useContext as useContext11, useState as useState7, useEffect as useEffect8, useRef as useRef6 } from "react";
9730
+ import { useContext as useContext11, useState as useState7, useEffect as useEffect9, useRef as useRef7 } from "react";
10061
9731
  var EMPTY_PARAMS = Object.freeze({});
10062
9732
 
10063
9733
  // src/browser/react/use-action.ts
10064
9734
  import {
10065
9735
  useContext as useContext12,
10066
9736
  useState as useState8,
10067
- useEffect as useEffect9,
10068
- useRef as useRef7,
9737
+ useEffect as useEffect10,
9738
+ useRef as useRef8,
10069
9739
  useOptimistic as useOptimistic4,
10070
9740
  startTransition as startTransition4
10071
9741
  } from "react";
10072
9742
 
10073
9743
  // src/browser/react/use-segments.ts
10074
- import { useContext as useContext13, useState as useState9, useEffect as useEffect10, useRef as useRef8 } from "react";
9744
+ import { useContext as useContext13, useState as useState9, useEffect as useEffect11, useRef as useRef9 } from "react";
10075
9745
 
10076
9746
  // src/browser/react/NavigationProvider.tsx
10077
9747
  import {
10078
9748
  useState as useState12,
10079
- useEffect as useEffect12,
10080
- useLayoutEffect,
9749
+ useEffect as useEffect13,
9750
+ useLayoutEffect as useLayoutEffect2,
10081
9751
  useCallback as useCallback4,
10082
- useMemo as useMemo6,
10083
- useRef as useRef10,
10084
- use as use3
9752
+ useMemo as useMemo7,
9753
+ useRef as useRef11,
9754
+ use as use3,
9755
+ startTransition as startTransition5
10085
9756
  } from "react";
10086
9757
 
10087
9758
  // src/root-error-boundary.tsx
@@ -10091,8 +9762,8 @@ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
10091
9762
  // src/theme/ThemeProvider.tsx
10092
9763
  import {
10093
9764
  useCallback as useCallback3,
10094
- useEffect as useEffect11,
10095
- useMemo as useMemo5,
9765
+ useEffect as useEffect12,
9766
+ useMemo as useMemo6,
10096
9767
  useState as useState11
10097
9768
  } from "react";
10098
9769
 
@@ -10128,10 +9799,9 @@ import {
10128
9799
  forwardRef,
10129
9800
  useCallback as useCallback5,
10130
9801
  useContext as useContext15,
10131
- useEffect as useEffect14,
10132
- useMemo as useMemo7,
10133
- useRef as useRef12,
10134
- useSyncExternalStore as useSyncExternalStore2
9802
+ useEffect as useEffect15,
9803
+ useMemo as useMemo8,
9804
+ useRef as useRef13
10135
9805
  } from "react";
10136
9806
 
10137
9807
  // src/browser/react/use-link-status.ts
@@ -10139,10 +9809,10 @@ import {
10139
9809
  createContext as createContext7,
10140
9810
  useContext as useContext14,
10141
9811
  useState as useState13,
10142
- useEffect as useEffect13,
10143
- useRef as useRef11,
9812
+ useEffect as useEffect14,
9813
+ useRef as useRef12,
10144
9814
  useOptimistic as useOptimistic5,
10145
- startTransition as startTransition5
9815
+ startTransition as startTransition6
10146
9816
  } from "react";
10147
9817
  var LinkContext = createContext7(
10148
9818
  null
@@ -10202,24 +9872,18 @@ function subscribeToLocationChange(eventController, listener) {
10202
9872
  }
10203
9873
 
10204
9874
  // src/browser/prefetch/default-strategy.ts
10205
- var hoverNoneQuery = null;
10206
- var defaultStrategy = process.env.NODE_ENV === "production" ? "viewport" : "none";
10207
- function getHoverNoneQuery() {
10208
- if (typeof window === "undefined") return null;
10209
- if (!hoverNoneQuery) {
10210
- hoverNoneQuery = window.matchMedia("(hover: none)");
10211
- }
10212
- return hoverNoneQuery;
10213
- }
10214
- function getDefaultPrefetchStrategy() {
10215
- return defaultStrategy;
9875
+ var ENVIRONMENT_DEFAULT_PREFETCH = process.env.NODE_ENV === "production" ? "viewport" : "none";
9876
+ function hoverNoneQuery() {
9877
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
9878
+ return null;
9879
+ return window.matchMedia("(hover: none)");
10216
9880
  }
10217
9881
  function resolveAdaptiveStrategy(strategy) {
10218
9882
  if (strategy !== "adaptive") return strategy;
10219
- return getHoverNoneQuery()?.matches ? "viewport" : "hover";
9883
+ return hoverNoneQuery()?.matches ? "viewport" : "hover";
10220
9884
  }
10221
9885
  function subscribeToAdaptiveStrategyChange(listener) {
10222
- const query = getHoverNoneQuery();
9886
+ const query = hoverNoneQuery();
10223
9887
  if (!query) return () => {
10224
9888
  };
10225
9889
  if (typeof query.addEventListener === "function") {
@@ -10403,23 +10067,16 @@ var Link = forwardRef(function Link2({
10403
10067
  ...props
10404
10068
  }, ref) {
10405
10069
  const ctx = useContext15(NavigationStoreContext);
10406
- const storeLocation = ctx?.eventController.getState().location;
10407
- const origin = storeLocation?.origin ?? (typeof window !== "undefined" ? window.location.origin : void 0);
10408
- const isExternal = isExternalUrl(to, origin);
10409
- const resolvedTo = useMemo7(() => {
10070
+ const isExternal = isExternalUrl(to, ctx?.origin);
10071
+ const resolvedTo = useMemo8(() => {
10410
10072
  if (isExternal) return to;
10411
10073
  const bn = ctx?.basename;
10412
10074
  if (!bn || !to.startsWith("/") || to.startsWith(bn + "/") || to === bn)
10413
10075
  return to;
10414
10076
  return to === "/" ? bn : bn + to;
10415
10077
  }, [to, isExternal, ctx?.basename]);
10416
- const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? getDefaultPrefetchStrategy();
10417
- const resolvedStrategy = useSyncExternalStore2(
10418
- configuredStrategy === "adaptive" ? subscribeToAdaptiveStrategyChange : subscribeToNothing,
10419
- () => resolveAdaptiveStrategy(configuredStrategy),
10420
- () => configuredStrategy === "adaptive" ? "hover" : configuredStrategy
10421
- );
10422
- const internalRef = useRef12(null);
10078
+ const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? ENVIRONMENT_DEFAULT_PREFETCH;
10079
+ const internalRef = useRef13(null);
10423
10080
  const setRef = useCallback5(
10424
10081
  (node) => {
10425
10082
  internalRef.current = node;
@@ -10431,7 +10088,7 @@ var Link = forwardRef(function Link2({
10431
10088
  },
10432
10089
  [ref]
10433
10090
  );
10434
- const stateRef = useRef12(state);
10091
+ const stateRef = useRef13(state);
10435
10092
  stateRef.current = state;
10436
10093
  const handleClick = useCallback5(
10437
10094
  (e) => {
@@ -10476,7 +10133,8 @@ var Link = forwardRef(function Link2({
10476
10133
  );
10477
10134
  const handleMouseEnter = useCallback5(() => {
10478
10135
  const element = internalRef.current;
10479
- if ((resolvedStrategy === "hover" || resolvedStrategy === "viewport") && !isExternal && ctx?.store && (!element || !isHashOnlyNavigation(element) && !isPrefetchScopeDisabled(element))) {
10136
+ const strategy = resolveAdaptiveStrategy(configuredStrategy);
10137
+ if ((strategy === "hover" || strategy === "viewport") && !isExternal && ctx?.store && (!element || !isHashOnlyNavigation(element) && !isPrefetchScopeDisabled(element))) {
10480
10138
  const segmentState = ctx.store.getSegmentState();
10481
10139
  prefetchDirect2(
10482
10140
  resolvedTo,
@@ -10486,13 +10144,18 @@ var Link = forwardRef(function Link2({
10486
10144
  prefetchKey
10487
10145
  );
10488
10146
  }
10489
- }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
10490
- useEffect14(() => {
10147
+ }, [configuredStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
10148
+ useEffect15(() => {
10491
10149
  if (isExternal || !ctx?.store) return;
10492
- const isViewport = resolvedStrategy === "viewport";
10493
- const isRender = resolvedStrategy === "render";
10494
- if (!isViewport && !isRender) return;
10150
+ if (configuredStrategy !== "viewport" && configuredStrategy !== "render" && configuredStrategy !== "adaptive") {
10151
+ return;
10152
+ }
10495
10153
  const armPrefetch = () => {
10154
+ const strategy = resolveAdaptiveStrategy(configuredStrategy);
10155
+ const isViewport = strategy === "viewport";
10156
+ const isRender = strategy === "render";
10157
+ if (!isViewport && !isRender) return () => {
10158
+ };
10496
10159
  const element2 = internalRef.current;
10497
10160
  if (element2 && (isHashOnlyNavigation(element2) || isPrefetchScopeDisabled(element2))) {
10498
10161
  return () => {
@@ -10537,24 +10200,24 @@ var Link = forwardRef(function Link2({
10537
10200
  };
10538
10201
  };
10539
10202
  let disarmPrefetch = armPrefetch();
10540
- const element = internalRef.current;
10541
- const unsubscribeScope = element ? subscribeToPrefetchScopeChange(element, () => {
10203
+ const rearm = () => {
10542
10204
  disarmPrefetch();
10543
10205
  disarmPrefetch = armPrefetch();
10544
- }) : void 0;
10206
+ };
10207
+ const element = internalRef.current;
10208
+ const unsubscribeScope = element ? subscribeToPrefetchScopeChange(element, rearm) : void 0;
10545
10209
  const unsubscribeLocation = subscribeToLocationChange(
10546
10210
  ctx.eventController,
10547
- () => {
10548
- disarmPrefetch();
10549
- disarmPrefetch = armPrefetch();
10550
- }
10211
+ rearm
10551
10212
  );
10213
+ const unsubscribeAdaptive = configuredStrategy === "adaptive" ? subscribeToAdaptiveStrategyChange(rearm) : void 0;
10552
10214
  return () => {
10553
10215
  unsubscribeScope?.();
10554
10216
  unsubscribeLocation();
10217
+ unsubscribeAdaptive?.();
10555
10218
  disarmPrefetch();
10556
10219
  };
10557
- }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
10220
+ }, [configuredStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
10558
10221
  return /* @__PURE__ */ jsx8(
10559
10222
  "a",
10560
10223
  {
@@ -10602,7 +10265,7 @@ var Breadcrumbs = createHandle(
10602
10265
  );
10603
10266
 
10604
10267
  // src/browser/react/location-state.ts
10605
- import { useContext as useContext16, useEffect as useEffect15, useSyncExternalStore as useSyncExternalStore3 } from "react";
10268
+ import { useContext as useContext16, useEffect as useEffect16 } from "react";
10606
10269
 
10607
10270
  // src/href-client.ts
10608
10271
  function href(path6, mount) {
@@ -10671,7 +10334,7 @@ import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
10671
10334
  init_cookie_name();
10672
10335
 
10673
10336
  // src/router/segment-resolution/mask-nested.ts
10674
- import { cloneElement, isValidElement as isValidElement2 } from "react";
10337
+ import { cloneElement, isValidElement as isValidElement3 } from "react";
10675
10338
 
10676
10339
  // src/server/handle-store.ts
10677
10340
  var LOADER_SLOT = Object.freeze({ loader: true });
@@ -10734,6 +10397,7 @@ var THEME_READ = {
10734
10397
 
10735
10398
  // src/cache/invalidation-order.ts
10736
10399
  var order = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:tag-invalidation-order")] ??= { seq: 0, tags: /* @__PURE__ */ new Map(), forgotten: 0 };
10400
+ var responseStarts = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:response-execution-start")] ??= /* @__PURE__ */ new WeakMap();
10737
10401
 
10738
10402
  // src/server/request-context.ts
10739
10403
  var requestContextStorage = new AsyncLocalStorage5();
@@ -10743,17 +10407,17 @@ init_redirect_origin();
10743
10407
 
10744
10408
  // src/client-urls/client-root.tsx
10745
10409
  import {
10746
- createElement as createElement4,
10410
+ createElement as createElement5,
10747
10411
  Fragment as Fragment4,
10748
10412
  Suspense as Suspense3,
10749
10413
  useDeferredValue,
10750
- useEffect as useEffect16,
10751
- useMemo as useMemo9,
10414
+ useEffect as useEffect17,
10415
+ useMemo as useMemo10,
10752
10416
  useState as useState14
10753
10417
  } from "react";
10754
10418
 
10755
10419
  // src/client-urls/navigation.ts
10756
- import { startTransition as startTransition6 } from "react";
10420
+ import { startTransition as startTransition7 } from "react";
10757
10421
 
10758
10422
  // src/client-urls/client-root.tsx
10759
10423
  import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -11285,11 +10949,23 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
11285
10949
  "inner: trie build done (%sms)",
11286
10950
  (performance.now() - trieStart).toFixed(1)
11287
10951
  );
10952
+ const newOnDemandHandlerIds = /* @__PURE__ */ new Map();
10953
+ for (const { manifest } of allManifests) {
10954
+ if (!manifest.onDemandRoutes) continue;
10955
+ const defs = manifest._prerenderDefs || {};
10956
+ for (const routeName of manifest.onDemandRoutes) {
10957
+ const handlerId = defs[routeName]?.$$id;
10958
+ if (typeof handlerId === "string") {
10959
+ newOnDemandHandlerIds.set(handlerId, routeName);
10960
+ }
10961
+ }
10962
+ }
11288
10963
  state.mergedRouteManifest = newMergedRouteManifest;
11289
10964
  state.perRouterManifests = newPerRouterManifests;
11290
10965
  state.perRouterManifestDataMap = newPerRouterManifestDataMap;
11291
10966
  state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
11292
10967
  state.perRouterTrieMap = newPerRouterTrieMap;
10968
+ state.onDemandHandlerIds = newOnDemandHandlerIds;
11293
10969
  const perRouterSetters = [
11294
10970
  [newPerRouterManifestDataMap, "setRouterManifest"],
11295
10971
  [newPerRouterTrieMap, "setRouterTrie"],
@@ -11310,8 +10986,8 @@ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as
11310
10986
  import { join as join7, resolve as resolve10 } from "node:path";
11311
10987
 
11312
10988
  // src/prerender/shell-manifest-key.ts
11313
- function buildShellManifestKey(pathname) {
11314
- return pathname;
10989
+ function buildShellManifestKey(routerId2, pathname) {
10990
+ return `${routerKeyPrefix(routerId2)}${pathname}`;
11315
10991
  }
11316
10992
 
11317
10993
  // src/vite/discovery/router-versions-phase.ts
@@ -11801,7 +11477,7 @@ async function runShellPrerenderPhase(s, builder, versionsTable) {
11801
11477
  tags: res.tags
11802
11478
  };
11803
11479
  staged.push({
11804
- key: buildShellManifestKey(cand.urlPath),
11480
+ key: buildShellManifestKey(routerId2, cand.urlPath),
11805
11481
  value: JSON.stringify(value)
11806
11482
  });
11807
11483
  console.log(
@@ -13012,6 +12688,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
13012
12688
  globalThis.__rscRouterDiscoveryActive ?? false
13013
12689
  );
13014
12690
  s.devServerOrigin = getDevServerOrigin();
12691
+ globalThis.__PRERENDER_DEV_URL = s.devServerOrigin;
13015
12692
  let tempRscEnv;
13016
12693
  let optimizerHashBefore2;
13017
12694
  try {
@@ -14065,7 +13742,13 @@ ${details}`,
14065
13742
  chunk.code,
14066
13743
  s.resolvedPrerenderModules,
14067
13744
  "Prerender",
14068
- false
13745
+ false,
13746
+ // Mark onDemand exports (retained, not evicted) from the
13747
+ // discovery-derived $$id set — the same evaluated source that sets
13748
+ // the runtime od trie flag, so retention and serve can never
13749
+ // disagree. Populated by discoverRouters (buildStart), which runs
13750
+ // before this generateBundle pass.
13751
+ s.onDemandHandlerIds
14069
13752
  );
14070
13753
  if (handlers.length > 0) {
14071
13754
  const existing = s.handlerChunkInfoMap.get(fileName);