qalita 2.5.4__py3-none-any.whl → 2.6.1__py3-none-any.whl

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 (213) hide show
  1. qalita/_frontend/.next/static/_0oyfpC7N2IGDMa04TT9R/_clientMiddlewareManifest.json +6 -0
  2. qalita/_frontend/.next/static/chunks/236f7e5abd6f09ff.js +4 -0
  3. qalita/_frontend/.next/static/chunks/30ea11065999f7ac.js +1 -0
  4. qalita/_frontend/.next/static/chunks/4dd28bc3f722184a.js +2 -0
  5. qalita/_frontend/.next/static/chunks/711d597b816a80c1.js +1 -0
  6. qalita/_frontend/.next/static/chunks/7340adf74ff47ec0.js +1 -0
  7. qalita/_frontend/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  8. qalita/_frontend/.next/static/chunks/{e393fec0d8ba175d.js → ecf559101be0ae12.js} +3 -3
  9. qalita/_frontend/.next/static/chunks/turbopack-25186fc8e1264445.js +4 -0
  10. qalita/_frontend/node_modules/@img/sharp-libvips-linux-x64/versions.json +30 -0
  11. qalita/_frontend/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
  12. qalita/_frontend/node_modules/@next/env/package.json +4 -4
  13. qalita/_frontend/node_modules/next/dist/build/analyze/index.js +242 -0
  14. qalita/_frontend/node_modules/next/dist/build/define-env.js +22 -13
  15. qalita/_frontend/node_modules/next/dist/build/entries.js +5 -5
  16. qalita/_frontend/node_modules/next/dist/build/generate-routes-manifest.js +91 -0
  17. qalita/_frontend/node_modules/next/dist/build/index.js +172 -223
  18. qalita/_frontend/node_modules/next/dist/build/output/log.js +4 -7
  19. qalita/_frontend/node_modules/next/dist/build/segment-config/app/app-segments.js +23 -99
  20. qalita/_frontend/node_modules/next/dist/build/segment-config/app/collect-root-param-keys.js +3 -3
  21. qalita/_frontend/node_modules/next/dist/build/spinner.js +3 -3
  22. qalita/_frontend/node_modules/next/dist/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.js +137 -0
  23. qalita/_frontend/node_modules/next/dist/build/static-paths/app.js +69 -210
  24. qalita/_frontend/node_modules/next/dist/build/static-paths/utils.js +83 -11
  25. qalita/_frontend/node_modules/next/dist/build/swc/index.js +10 -6
  26. qalita/_frontend/node_modules/next/dist/build/templates/app-page.js +21 -14
  27. qalita/_frontend/node_modules/next/dist/build/templates/app-route.js +8 -10
  28. qalita/_frontend/node_modules/next/dist/build/templates/edge-app-route.js +7 -10
  29. qalita/_frontend/node_modules/next/dist/build/templates/edge-ssr-app.js +16 -20
  30. qalita/_frontend/node_modules/next/dist/build/templates/edge-ssr.js +20 -14
  31. qalita/_frontend/node_modules/next/dist/build/templates/edge-wrapper.js +24 -0
  32. qalita/_frontend/node_modules/next/dist/build/templates/middleware.js +7 -6
  33. qalita/_frontend/node_modules/next/dist/build/templates/pages-edge-api.js +3 -2
  34. qalita/_frontend/node_modules/next/dist/build/turbopack-analyze/index.js +116 -0
  35. qalita/_frontend/node_modules/next/dist/build/turbopack-build/impl.js +2 -1
  36. qalita/_frontend/node_modules/next/dist/build/type-check.js +7 -8
  37. qalita/_frontend/node_modules/next/dist/build/utils.js +45 -4
  38. qalita/_frontend/node_modules/next/dist/build/validate-app-paths.js +242 -0
  39. qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.js +20 -2
  40. qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-edge-app-route-loader/index.js +2 -5
  41. qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-edge-ssr-loader/index.js +4 -11
  42. qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-root-params-loader.js +1 -1
  43. qalita/_frontend/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.js +1 -1
  44. qalita/_frontend/node_modules/next/dist/build/webpack/plugins/middleware-plugin.js +3 -0
  45. qalita/_frontend/node_modules/next/dist/build/webpack-build/impl.js +1 -0
  46. qalita/_frontend/node_modules/next/dist/build/webpack-config.js +35 -3
  47. qalita/_frontend/node_modules/next/dist/cli/next-test.js +3 -5
  48. qalita/_frontend/node_modules/next/dist/client/components/app-router-headers.js +5 -0
  49. qalita/_frontend/node_modules/next/dist/client/components/app-router-instance.js +3 -11
  50. qalita/_frontend/node_modules/next/dist/client/components/app-router.js +8 -28
  51. qalita/_frontend/node_modules/next/dist/client/components/navigation-devtools.js +5 -7
  52. qalita/_frontend/node_modules/next/dist/client/components/navigation.js +4 -3
  53. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/create-initial-router-state.js +3 -22
  54. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fetch-server-response.js +6 -18
  55. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/ppr-navigations.js +844 -590
  56. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/hmr-refresh-reducer.js +4 -76
  57. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/navigate-reducer.js +21 -18
  58. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/refresh-reducer.js +48 -85
  59. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/restore-reducer.js +25 -10
  60. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/server-action-reducer.js +126 -113
  61. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/server-patch-reducer.js +30 -39
  62. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/router-reducer-types.js +0 -1
  63. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/router-reducer.js +2 -2
  64. qalita/_frontend/node_modules/next/dist/client/components/segment-cache/cache-map.js +13 -18
  65. qalita/_frontend/node_modules/next/dist/client/components/segment-cache/cache.js +27 -14
  66. qalita/_frontend/node_modules/next/dist/client/components/segment-cache/lru.js +3 -1
  67. qalita/_frontend/node_modules/next/dist/client/components/segment-cache/navigation.js +176 -133
  68. qalita/_frontend/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +12 -12
  69. qalita/_frontend/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +12 -12
  70. qalita/_frontend/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  71. qalita/_frontend/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +10 -10
  72. qalita/_frontend/node_modules/next/dist/compiled/react-is/package.json +1 -1
  73. qalita/_frontend/node_modules/next/dist/lib/build-custom-route.js +4 -4
  74. qalita/_frontend/node_modules/next/dist/lib/constants.js +0 -5
  75. qalita/_frontend/node_modules/next/dist/lib/default-transpiled-packages.json +1 -0
  76. qalita/_frontend/node_modules/next/dist/lib/generate-interception-routes-rewrites.js +0 -1
  77. qalita/_frontend/node_modules/next/dist/lib/helpers/get-npx-command.js +3 -3
  78. qalita/_frontend/node_modules/next/dist/lib/inline-static-env.js +1 -1
  79. qalita/_frontend/node_modules/next/dist/lib/known-edge-safe-packages.json +1 -0
  80. qalita/_frontend/node_modules/next/dist/lib/metadata/metadata.js +17 -11
  81. qalita/_frontend/node_modules/next/dist/lib/needs-experimental-react.js +2 -2
  82. qalita/_frontend/node_modules/next/dist/lib/resolve-build-paths.js +44 -76
  83. qalita/_frontend/node_modules/next/dist/lib/server-external-packages.jsonc +103 -0
  84. qalita/_frontend/node_modules/next/dist/lib/static-env.js +6 -6
  85. qalita/_frontend/node_modules/next/dist/lib/turbopack-warning.js +3 -5
  86. qalita/_frontend/node_modules/next/dist/lib/typescript/runTypeCheck.js +61 -5
  87. qalita/_frontend/node_modules/next/dist/lib/typescript/type-paths.js +56 -0
  88. qalita/_frontend/node_modules/next/dist/lib/typescript/writeConfigurationDefaults.js +6 -17
  89. qalita/_frontend/node_modules/next/dist/lib/verify-typescript-setup.js +10 -2
  90. qalita/_frontend/node_modules/next/dist/lib/worker.js +17 -9
  91. qalita/_frontend/node_modules/next/dist/server/app-render/action-handler.js +113 -77
  92. qalita/_frontend/node_modules/next/dist/server/app-render/app-render-prerender-utils.js +36 -15
  93. qalita/_frontend/node_modules/next/dist/server/app-render/app-render-render-utils.js +36 -15
  94. qalita/_frontend/node_modules/next/dist/server/app-render/app-render-scheduling.js +188 -0
  95. qalita/_frontend/node_modules/next/dist/server/app-render/app-render.js +804 -748
  96. qalita/_frontend/node_modules/next/dist/server/app-render/collect-segment-data.js +8 -2
  97. qalita/_frontend/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +1 -1
  98. qalita/_frontend/node_modules/next/dist/server/app-render/create-error-handler.js +42 -75
  99. qalita/_frontend/node_modules/next/dist/server/app-render/dynamic-rendering.js +127 -14
  100. qalita/_frontend/node_modules/next/dist/server/app-render/encryption-utils.js +3 -108
  101. qalita/_frontend/node_modules/next/dist/server/app-render/encryption.js +4 -3
  102. qalita/_frontend/node_modules/next/dist/server/app-render/flight-render-result.js +3 -2
  103. qalita/_frontend/node_modules/next/dist/server/app-render/get-css-inlined-link-tags.js +9 -8
  104. qalita/_frontend/node_modules/next/dist/server/app-render/get-layer-assets.js +1 -1
  105. qalita/_frontend/node_modules/next/dist/server/app-render/manifests-singleton.js +257 -0
  106. qalita/_frontend/node_modules/next/dist/server/app-render/prospective-render-utils.js +25 -8
  107. qalita/_frontend/node_modules/next/dist/server/app-render/staged-rendering.js +161 -15
  108. qalita/_frontend/node_modules/next/dist/server/app-render/use-flight-response.js +67 -20
  109. qalita/_frontend/node_modules/next/dist/server/app-render/walk-tree-with-flight-router-state.js +2 -2
  110. qalita/_frontend/node_modules/next/dist/server/async-storage/work-store.js +2 -1
  111. qalita/_frontend/node_modules/next/dist/server/base-server.js +32 -23
  112. qalita/_frontend/node_modules/next/dist/server/capsize-font-metrics.json +181516 -0
  113. qalita/_frontend/node_modules/next/dist/server/config-schema.js +8 -1
  114. qalita/_frontend/node_modules/next/dist/server/config-shared.js +83 -2
  115. qalita/_frontend/node_modules/next/dist/server/config.js +24 -17
  116. qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-rspack.js +171 -0
  117. qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +37 -25
  118. qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-types.js +1 -0
  119. qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-webpack.js +62 -57
  120. qalita/_frontend/node_modules/next/dist/server/dev/log-requests.js +11 -3
  121. qalita/_frontend/node_modules/next/dist/server/dev/messages.js +10 -0
  122. qalita/_frontend/node_modules/next/dist/server/dev/serialized-errors.js +67 -0
  123. qalita/_frontend/node_modules/next/dist/server/dev/static-paths-worker.js +10 -0
  124. qalita/_frontend/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  125. qalita/_frontend/node_modules/next/dist/server/lib/chrome-devtools-workspace.js +2 -2
  126. qalita/_frontend/node_modules/next/dist/server/lib/dev-bundler-service.js +6 -10
  127. qalita/_frontend/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +4 -4
  128. qalita/_frontend/node_modules/next/dist/server/lib/lazy-result.js +1 -1
  129. qalita/_frontend/node_modules/next/dist/server/lib/patch-fetch.js +9 -9
  130. qalita/_frontend/node_modules/next/dist/server/lib/router-server.js +43 -29
  131. qalita/_frontend/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.js +0 -21
  132. qalita/_frontend/node_modules/next/dist/server/lib/router-utils/filesystem.js +2 -7
  133. qalita/_frontend/node_modules/next/dist/server/lib/router-utils/resolve-routes.js +8 -4
  134. qalita/_frontend/node_modules/next/dist/server/lib/start-server.js +3 -3
  135. qalita/_frontend/node_modules/next/dist/server/lib/trace/tracer.js +4 -0
  136. qalita/_frontend/node_modules/next/dist/server/load-components.js +3 -9
  137. qalita/_frontend/node_modules/next/dist/server/next-server.js +19 -18
  138. qalita/_frontend/node_modules/next/dist/server/next.js +1 -1
  139. qalita/_frontend/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.js +570 -0
  140. qalita/_frontend/node_modules/next/dist/server/node-environment-extensions/utils.js +60 -3
  141. qalita/_frontend/node_modules/next/dist/server/node-environment.js +1 -0
  142. qalita/_frontend/node_modules/next/dist/server/patch-error-inspect.js +7 -4
  143. qalita/_frontend/node_modules/next/dist/server/request/fallback-params.js +23 -66
  144. qalita/_frontend/node_modules/next/dist/server/route-modules/app-route/module.js +2 -2
  145. qalita/_frontend/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -3
  146. qalita/_frontend/node_modules/next/dist/server/route-modules/route-module.js +79 -18
  147. qalita/_frontend/node_modules/next/dist/server/stream-utils/node-web-streams-helper.js +28 -4
  148. qalita/_frontend/node_modules/next/dist/server/typescript/rules/config.js +50 -6
  149. qalita/_frontend/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +68 -23
  150. qalita/_frontend/node_modules/next/dist/server/web/edge-route-module-wrapper.js +7 -5
  151. qalita/_frontend/node_modules/next/dist/server/web/sandbox/context.js +8 -9
  152. qalita/_frontend/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.js +2 -1
  153. qalita/_frontend/node_modules/next/dist/server/web/spec-extension/revalidate.js +7 -3
  154. qalita/_frontend/node_modules/next/dist/shared/lib/action-revalidation-kind.js +31 -0
  155. qalita/_frontend/node_modules/next/dist/shared/lib/constants.js +6 -1
  156. qalita/_frontend/node_modules/next/dist/shared/lib/deployment-id.js +36 -0
  157. qalita/_frontend/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  158. qalita/_frontend/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.js +5 -0
  159. qalita/_frontend/node_modules/next/dist/shared/lib/router/routes/app.js +122 -0
  160. qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/get-dynamic-param.js +20 -49
  161. qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/get-segment-param.js +6 -6
  162. qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/interception-prefix-from-param-type.js +33 -0
  163. qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/resolve-param-value.js +116 -0
  164. qalita/_frontend/node_modules/next/dist/shared/lib/segment-cache/output-export-prefetch-encoding.js +3 -24
  165. qalita/_frontend/node_modules/next/dist/shared/lib/segment.js +5 -0
  166. qalita/_frontend/node_modules/next/dist/shared/lib/turbopack/utils.js +6 -5
  167. qalita/_frontend/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  168. qalita/_frontend/node_modules/next/dist/telemetry/events/build.js +11 -0
  169. qalita/_frontend/node_modules/next/dist/telemetry/events/version.js +2 -2
  170. qalita/_frontend/node_modules/next/package.json +20 -18
  171. qalita/_frontend/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
  172. qalita/_frontend/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
  173. qalita/_frontend/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
  174. qalita/_frontend/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
  175. qalita/_frontend/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
  176. qalita/_frontend/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
  177. qalita/_frontend/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
  178. qalita/_frontend/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
  179. qalita/_frontend/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
  180. qalita/_frontend/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
  181. qalita/_frontend/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
  182. qalita/_frontend/node_modules/typescript/lib/typesMap.json +497 -0
  183. qalita/_frontend/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
  184. qalita/_frontend/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
  185. qalita/_frontend/package.json +3 -3
  186. qalita/_frontend/server.js +1 -1
  187. qalita/commands/pack.py +3 -3
  188. qalita/commands/source.py +1 -1
  189. qalita/internal/utils.py +11 -7
  190. qalita/web/app.py +20 -4
  191. {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/METADATA +1 -1
  192. {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/RECORD +197 -168
  193. qalita/_frontend/.next/static/chunks/023d923a37d494fc.js +0 -1
  194. qalita/_frontend/.next/static/chunks/247eb132b7f7b574.js +0 -1
  195. qalita/_frontend/.next/static/chunks/bba035711c7e37a2.js +0 -4
  196. qalita/_frontend/.next/static/chunks/c903f9580a4b6572.js +0 -2
  197. qalita/_frontend/.next/static/chunks/cbd55ab9639e1e66.js +0 -1
  198. qalita/_frontend/.next/static/chunks/turbopack-1ad58da399056f41.js +0 -3
  199. qalita/_frontend/node_modules/next/dist/build/deployment-id.js +0 -18
  200. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/apply-flight-data.js +0 -53
  201. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/apply-router-state-patch-to-tree.js +0 -105
  202. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fill-cache-with-new-subtree-data.js +0 -110
  203. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.js +0 -131
  204. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/handle-segment-mismatch.js +0 -25
  205. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/invalidate-cache-by-router-state.js +0 -32
  206. qalita/_frontend/node_modules/next/dist/client/components/router-reducer/refetch-inactive-parallel-segments.js +0 -104
  207. qalita/_frontend/node_modules/next/dist/server/app-render/action-utils.js +0 -90
  208. qalita/_frontend/node_modules/next/dist/server/normalizers/request/prefetch-rsc.js +0 -31
  209. /qalita/_frontend/.next/static/{X4_AlYMbCyee-ZVLjCYMg → _0oyfpC7N2IGDMa04TT9R}/_buildManifest.js +0 -0
  210. /qalita/_frontend/.next/static/{X4_AlYMbCyee-ZVLjCYMg → _0oyfpC7N2IGDMa04TT9R}/_ssgManifest.js +0 -0
  211. {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/WHEEL +0 -0
  212. {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/entry_points.txt +0 -0
  213. {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  0 && (module.exports = {
6
- abortTask: null,
7
- listenForDynamicRequest: null,
8
- startPPRNavigation: null,
9
- updateCacheNodeOnPopstateRestoration: null
6
+ FreshnessPolicy: null,
7
+ createInitialCacheNodeForHydration: null,
8
+ isDeferredRsc: null,
9
+ spawnDynamicRequests: null,
10
+ startPPRNavigation: null
10
11
  });
11
12
  function _export(target, all) {
12
13
  for(var name in all)Object.defineProperty(target, name, {
@@ -15,51 +16,139 @@ function _export(target, all) {
15
16
  });
16
17
  }
17
18
  _export(exports, {
18
- abortTask: function() {
19
- return abortTask;
19
+ FreshnessPolicy: function() {
20
+ return FreshnessPolicy;
20
21
  },
21
- listenForDynamicRequest: function() {
22
- return listenForDynamicRequest;
22
+ createInitialCacheNodeForHydration: function() {
23
+ return createInitialCacheNodeForHydration;
24
+ },
25
+ isDeferredRsc: function() {
26
+ return isDeferredRsc;
27
+ },
28
+ spawnDynamicRequests: function() {
29
+ return spawnDynamicRequests;
23
30
  },
24
31
  startPPRNavigation: function() {
25
32
  return startPPRNavigation;
26
- },
27
- updateCacheNodeOnPopstateRestoration: function() {
28
- return updateCacheNodeOnPopstateRestoration;
29
33
  }
30
34
  });
31
35
  const _segment = require("../../../shared/lib/segment");
32
36
  const _matchsegments = require("../match-segments");
33
37
  const _createhreffromurl = require("./create-href-from-url");
34
38
  const _createroutercachekey = require("./create-router-cache-key");
39
+ const _fetchserverresponse = require("./fetch-server-response");
40
+ const _useactionqueue = require("../use-action-queue");
41
+ const _routerreducertypes = require("./router-reducer-types");
35
42
  const _isnavigatingtonewrootlayout = require("./is-navigating-to-new-root-layout");
36
43
  const _navigatereducer = require("./reducers/navigate-reducer");
37
- const MPA_NAVIGATION_TASK = {
38
- route: null,
39
- node: null,
40
- dynamicRequestTree: null,
41
- children: null
42
- };
43
- function startPPRNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, scrollableSegmentsResult) {
44
- const segmentPath = [];
45
- return updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, false, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, segmentPath, scrollableSegmentsResult);
44
+ const _navigation = require("../segment-cache/navigation");
45
+ var FreshnessPolicy = /*#__PURE__*/ function(FreshnessPolicy) {
46
+ FreshnessPolicy[FreshnessPolicy["Default"] = 0] = "Default";
47
+ FreshnessPolicy[FreshnessPolicy["Hydration"] = 1] = "Hydration";
48
+ FreshnessPolicy[FreshnessPolicy["HistoryTraversal"] = 2] = "HistoryTraversal";
49
+ FreshnessPolicy[FreshnessPolicy["RefreshAll"] = 3] = "RefreshAll";
50
+ FreshnessPolicy[FreshnessPolicy["HMRRefresh"] = 4] = "HMRRefresh";
51
+ return FreshnessPolicy;
52
+ }({});
53
+ const noop = ()=>{};
54
+ function createInitialCacheNodeForHydration(navigatedAt, initialTree, seedData, seedHead) {
55
+ // Create the initial cache node tree, using the data embedded into the
56
+ // HTML document.
57
+ const accumulation = {
58
+ scrollableSegments: null,
59
+ separateRefreshUrls: null
60
+ };
61
+ const task = createCacheNodeOnNavigation(navigatedAt, initialTree, undefined, 1, seedData, seedHead, null, null, false, null, null, false, accumulation);
62
+ // NOTE: We intentionally don't check if any data needs to be fetched from the
63
+ // server. We assume the initial hydration payload is sufficient to render
64
+ // the page.
65
+ //
66
+ // The completeness of the initial data is an important property that we rely
67
+ // on as a last-ditch mechanism for recovering the app; we must always be able
68
+ // to reload a fresh HTML document to get to a consistent state.
69
+ //
70
+ // In the future, there may be cases where the server intentionally sends
71
+ // partial data and expects the client to fill in the rest, in which case this
72
+ // logic may change. (There already is a similar case where the server sends
73
+ // _no_ hydration data in the HTML document at all, and the client fetches it
74
+ // separately, but that's different because we still end up hydrating with a
75
+ // complete tree.)
76
+ return task.node;
46
77
  }
47
- function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, didFindRootLayout, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, segmentPath, scrollableSegmentsResult) {
48
- // Diff the old and new trees to reuse the shared layouts.
49
- const oldRouterStateChildren = oldRouterState[1];
50
- const newRouterStateChildren = newRouterState[1];
51
- const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
52
- if (!didFindRootLayout) {
53
- // We're currently traversing the part of the tree that was also part of
54
- // the previous route. If we discover a root layout, then we don't need to
55
- // trigger an MPA navigation. See beginRenderingNewRouteTree for context.
56
- const isRootLayout = newRouterState[4] === true;
57
- if (isRootLayout) {
58
- // Found a matching root layout.
59
- didFindRootLayout = true;
78
+ function startPPRNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, accumulation) {
79
+ const didFindRootLayout = false;
80
+ const parentNeedsDynamicRequest = false;
81
+ const parentRefreshUrl = null;
82
+ return updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode !== null ? oldCacheNode : undefined, oldRouterState, newRouterState, freshness, didFindRootLayout, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, null, null, parentNeedsDynamicRequest, parentRefreshUrl, accumulation);
83
+ }
84
+ function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, freshness, didFindRootLayout, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, parentRefreshUrl, accumulation) {
85
+ // Check if this segment matches the one in the previous route.
86
+ const oldSegment = oldRouterState[0];
87
+ const newSegment = newRouterState[0];
88
+ if (!(0, _matchsegments.matchSegment)(newSegment, oldSegment)) {
89
+ // This segment does not match the previous route. We're now entering the
90
+ // new part of the target route. Switch to the "create" path.
91
+ if (// Check if the route tree changed before we reached a layout. (The
92
+ // highest-level layout in a route tree is referred to as the "root"
93
+ // layout.) This could mean that we're navigating between two different
94
+ // root layouts. When this happens, we perform a full-page (MPA-style)
95
+ // navigation.
96
+ //
97
+ // However, the algorithm for deciding where to start rendering a route
98
+ // (i.e. the one performed in order to reach this function) is stricter
99
+ // than the one used to detect a change in the root layout. So just
100
+ // because we're re-rendering a segment outside of the root layout does
101
+ // not mean we should trigger a full-page navigation.
102
+ //
103
+ // Specifically, we handle dynamic parameters differently: two segments
104
+ // are considered the same even if their parameter values are different.
105
+ //
106
+ // Refer to isNavigatingToNewRootLayout for details.
107
+ //
108
+ // Note that we only have to perform this extra traversal if we didn't
109
+ // already discover a root layout in the part of the tree that is
110
+ // unchanged. We also only need to compare the subtree that is not
111
+ // shared. In the common case, this branch is skipped completely.
112
+ !didFindRootLayout && (0, _isnavigatingtonewrootlayout.isNavigatingToNewRootLayout)(oldRouterState, newRouterState) || // The global Not Found route (app/global-not-found.tsx) is a special
113
+ // case, because it acts like a root layout, but in the router tree, it
114
+ // is rendered in the same position as app/layout.tsx.
115
+ //
116
+ // Any navigation to the global Not Found route should trigger a
117
+ // full-page navigation.
118
+ //
119
+ // TODO: We should probably model this by changing the key of the root
120
+ // segment when this happens. Then the root layout check would work
121
+ // as expected, without a special case.
122
+ newSegment === _segment.NOT_FOUND_SEGMENT_KEY) {
123
+ return null;
60
124
  }
125
+ if (parentSegmentPath === null || parentParallelRouteKey === null) {
126
+ // The root should never mismatch. If it does, it suggests an internal
127
+ // Next.js error, or a malformed server response. Trigger a full-
128
+ // page navigation.
129
+ return null;
130
+ }
131
+ return createCacheNodeOnNavigation(navigatedAt, newRouterState, oldCacheNode, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, accumulation);
61
132
  }
62
- const oldParallelRoutes = oldCacheNode.parallelRoutes;
133
+ // TODO: The segment paths are tracked so that LayoutRouter knows which
134
+ // segments to scroll to after a navigation. But we should just mark this
135
+ // information on the CacheNode directly. It used to be necessary to do this
136
+ // separately because CacheNodes were created lazily during render, not when
137
+ // rather than when creating the route tree.
138
+ const segmentPath = parentParallelRouteKey !== null && parentSegmentPath !== null ? parentSegmentPath.concat([
139
+ parentParallelRouteKey,
140
+ newSegment
141
+ ]) : [];
142
+ const newRouterStateChildren = newRouterState[1];
143
+ const oldRouterStateChildren = oldRouterState[1];
144
+ const seedDataChildren = seedData !== null ? seedData[1] : null;
145
+ const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
146
+ // We're currently traversing the part of the tree that was also part of
147
+ // the previous route. If we discover a root layout, then we don't need to
148
+ // trigger an MPA navigation.
149
+ const isRootLayout = newRouterState[4] === true;
150
+ const childDidFindRootLayout = didFindRootLayout || isRootLayout;
151
+ const oldParallelRoutes = oldCacheNode !== undefined ? oldCacheNode.parallelRoutes : undefined;
63
152
  // Clone the current set of segment children, even if they aren't active in
64
153
  // the new tree.
65
154
  // TODO: We currently retain all the inactive segments indefinitely, until
@@ -71,7 +160,84 @@ function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRoute
71
160
  // leak. We should figure out a better model for the lifetime of inactive
72
161
  // segments, so we can maintain instant back/forward navigations without
73
162
  // leaking memory indefinitely.
74
- const prefetchParallelRoutes = new Map(oldParallelRoutes);
163
+ let shouldDropSiblingCaches = false;
164
+ let shouldRefreshDynamicData = false;
165
+ switch(freshness){
166
+ case 0:
167
+ case 2:
168
+ case 1:
169
+ // We should never drop dynamic data in shared layouts, except during
170
+ // a refresh.
171
+ shouldDropSiblingCaches = false;
172
+ shouldRefreshDynamicData = false;
173
+ break;
174
+ case 3:
175
+ case 4:
176
+ shouldDropSiblingCaches = true;
177
+ shouldRefreshDynamicData = true;
178
+ break;
179
+ default:
180
+ freshness;
181
+ break;
182
+ }
183
+ const newParallelRoutes = new Map(shouldDropSiblingCaches ? undefined : oldParallelRoutes);
184
+ // TODO: We're not consistent about how we do this check. Some places
185
+ // check if the segment starts with PAGE_SEGMENT_KEY, but most seem to
186
+ // check if there any any children, which is why I'm doing it here. We
187
+ // should probably encode an empty children set as `null` though. Either
188
+ // way, we should update all the checks to be consistent.
189
+ const isLeafSegment = Object.keys(newRouterStateChildren).length === 0;
190
+ // Get the data for this segment. Since it was part of the previous route,
191
+ // usually we just clone the data from the old CacheNode. However, during a
192
+ // refresh or a revalidation, there won't be any existing CacheNode. So we
193
+ // may need to consult the prefetch cache, like we would for a new segment.
194
+ let newCacheNode;
195
+ let needsDynamicRequest;
196
+ if (oldCacheNode !== undefined && !shouldRefreshDynamicData && // During a same-page navigation, we always refetch the page segments
197
+ !(isLeafSegment && isSamePageNavigation)) {
198
+ // Reuse the existing CacheNode
199
+ const dropPrefetchRsc = false;
200
+ newCacheNode = reuseDynamicCacheNode(dropPrefetchRsc, oldCacheNode, newParallelRoutes);
201
+ needsDynamicRequest = false;
202
+ } else if (seedData !== null && seedData[0] !== null) {
203
+ // If this navigation was the result of an action, then check if the
204
+ // server sent back data in the action response. We should favor using
205
+ // that, rather than performing a separate request. This is both better
206
+ // for performance and it's more likely to be consistent with any
207
+ // writes that were just performed by the action, compared to a
208
+ // separate request.
209
+ const seedRsc = seedData[0];
210
+ const seedLoading = seedData[2];
211
+ const isSeedRscPartial = false;
212
+ const isSeedHeadPartial = seedHead === null;
213
+ newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
214
+ needsDynamicRequest = isLeafSegment && isSeedHeadPartial;
215
+ } else if (prefetchData !== null) {
216
+ // Consult the prefetch cache.
217
+ const prefetchRsc = prefetchData[0];
218
+ const prefetchLoading = prefetchData[2];
219
+ const isPrefetchRSCPartial = prefetchData[3];
220
+ newCacheNode = readCacheNodeFromSeedData(prefetchRsc, prefetchLoading, isPrefetchRSCPartial, prefetchHead, isPrefetchHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
221
+ needsDynamicRequest = isPrefetchRSCPartial || isLeafSegment && isPrefetchHeadPartial;
222
+ } else {
223
+ // Spawn a request to fetch new data from the server.
224
+ newCacheNode = spawnNewCacheNode(newParallelRoutes, isLeafSegment, navigatedAt, freshness);
225
+ needsDynamicRequest = true;
226
+ }
227
+ // During a refresh navigation, there's a special case that happens when
228
+ // entering a "default" slot. The default slot may not be part of the
229
+ // current route; it may have been reused from an older route. If so,
230
+ // we need to fetch its data from the old route's URL rather than current
231
+ // route's URL. Keep track of this as we traverse the tree.
232
+ const href = newRouterState[2];
233
+ const refreshUrl = typeof href === 'string' && newRouterState[3] === 'refresh' ? // refresh URL as we continue traversing the tree.
234
+ href : parentRefreshUrl;
235
+ // If this segment itself needs to fetch new data from the server, then by
236
+ // definition it is being refreshed. Track its refresh URL so we know which
237
+ // URL to request the data from.
238
+ if (needsDynamicRequest && refreshUrl !== null) {
239
+ accumulateRefreshUrl(accumulation, refreshUrl);
240
+ }
75
241
  // As we diff the trees, we may sometimes modify (copy-on-write, not mutate)
76
242
  // the Route Tree that was returned by the server — for example, in the case
77
243
  // of default parallel routes, we preserve the currently active segment. To
@@ -88,302 +254,266 @@ function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRoute
88
254
  //
89
255
  // This starts off as `false`, and is set to `true` if any of the child
90
256
  // routes requires a dynamic request.
91
- let needsDynamicRequest = false;
257
+ let childNeedsDynamicRequest = false;
92
258
  // As we traverse the children, we'll construct a FlightRouterState that can
93
259
  // be sent to the server to request the dynamic data. If it turns out that
94
- // nothing in the subtree is dynamic (i.e. needsDynamicRequest is false at the
95
- // end), then this will be discarded.
260
+ // nothing in the subtree is dynamic (i.e. childNeedsDynamicRequest is false
261
+ // at the end), then this will be discarded.
96
262
  // TODO: We can probably optimize the format of this data structure to only
97
263
  // include paths that are dynamic. Instead of reusing the
98
264
  // FlightRouterState type.
99
265
  let dynamicRequestTreeChildren = {};
100
266
  for(let parallelRouteKey in newRouterStateChildren){
101
- const newRouterStateChild = newRouterStateChildren[parallelRouteKey];
267
+ let newRouterStateChild = newRouterStateChildren[parallelRouteKey];
102
268
  const oldRouterStateChild = oldRouterStateChildren[parallelRouteKey];
103
- const oldSegmentMapChild = oldParallelRoutes.get(parallelRouteKey);
104
- const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
105
- const newSegmentChild = newRouterStateChild[0];
106
- const newSegmentPathChild = segmentPath.concat([
107
- parallelRouteKey,
108
- newSegmentChild
109
- ]);
269
+ if (oldRouterStateChild === undefined) {
270
+ // This should never happen, but if it does, it suggests a malformed
271
+ // server response. Trigger a full-page navigation.
272
+ return null;
273
+ }
274
+ const oldSegmentMapChild = oldParallelRoutes !== undefined ? oldParallelRoutes.get(parallelRouteKey) : undefined;
275
+ let seedDataChild = seedDataChildren !== null ? seedDataChildren[parallelRouteKey] : null;
276
+ let prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
277
+ let newSegmentChild = newRouterStateChild[0];
278
+ let seedHeadChild = seedHead;
279
+ let prefetchHeadChild = prefetchHead;
280
+ let isPrefetchHeadPartialChild = isPrefetchHeadPartial;
281
+ if (// Skip this branch during a history traversal. We restore the tree that
282
+ // was stashed in the history entry as-is.
283
+ freshness !== 2 && newSegmentChild === _segment.DEFAULT_SEGMENT_KEY) {
284
+ // This is a "default" segment. These are never sent by the server during
285
+ // a soft navigation; instead, the client reuses whatever segment was
286
+ // already active in that slot on the previous route.
287
+ newRouterStateChild = reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterStateChild);
288
+ newSegmentChild = newRouterStateChild[0];
289
+ // Since we're switching to a different route tree, these are no
290
+ // longer valid, because they correspond to the outer tree.
291
+ seedDataChild = null;
292
+ seedHeadChild = null;
293
+ prefetchDataChild = null;
294
+ prefetchHeadChild = null;
295
+ isPrefetchHeadPartialChild = false;
296
+ }
110
297
  const newSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(newSegmentChild);
111
- const oldSegmentChild = oldRouterStateChild !== undefined ? oldRouterStateChild[0] : undefined;
112
298
  const oldCacheNodeChild = oldSegmentMapChild !== undefined ? oldSegmentMapChild.get(newSegmentKeyChild) : undefined;
113
- let taskChild;
114
- if (newSegmentChild === _segment.DEFAULT_SEGMENT_KEY) {
115
- // This is another kind of leaf segment a default route.
116
- //
117
- // Default routes have special behavior. When there's no matching segment
118
- // for a parallel route, Next.js preserves the currently active segment
119
- // during a client navigation — but not for initial render. The server
120
- // leaves it to the client to account for this. So we need to handle
121
- // it here.
122
- if (oldRouterStateChild !== undefined) {
123
- // Reuse the existing Router State for this segment. We spawn a "task"
124
- // just to keep track of the updated router state; unlike most, it's
125
- // already fulfilled and won't be affected by the dynamic response.
126
- taskChild = reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterStateChild);
127
- } else {
128
- // There's no currently active segment. Switch to the "create" path.
129
- taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
130
- }
131
- } else if (isSamePageNavigation && // Check if this is a page segment.
132
- // TODO: We're not consistent about how we do this check. Some places
133
- // check if the segment starts with PAGE_SEGMENT_KEY, but most seem to
134
- // check if there any any children, which is why I'm doing it here. We
135
- // should probably encode an empty children set as `null` though. Either
136
- // way, we should update all the checks to be consistent.
137
- Object.keys(newRouterStateChild[1]).length === 0) {
138
- // We special case navigations to the exact same URL as the current
139
- // location. It's a common UI pattern for apps to refresh when you click a
140
- // link to the current page. So when this happens, we refresh the dynamic
141
- // data in the page segments.
142
- //
143
- // Note that this does not apply if the any part of the hash or search
144
- // query has changed. This might feel a bit weird but it makes more sense
145
- // when you consider that the way to trigger this behavior is to click
146
- // the same link multiple times.
147
- //
148
- // TODO: We should probably refresh the *entire* route when this case
149
- // occurs, not just the page segments. Essentially treating it the same as
150
- // a refresh() triggered by an action, which is the more explicit way of
151
- // modeling the UI pattern described above.
152
- //
153
- // Also note that this only refreshes the dynamic data, not static/
154
- // cached data. If the page segment is fully static and prefetched, the
155
- // request is skipped. (This is also how refresh() works.)
156
- taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
157
- } else if (oldRouterStateChild !== undefined && oldSegmentChild !== undefined && (0, _matchsegments.matchSegment)(newSegmentChild, oldSegmentChild)) {
158
- if (oldCacheNodeChild !== undefined && oldRouterStateChild !== undefined) {
159
- // This segment exists in both the old and new trees. Recursively update
160
- // the children.
161
- taskChild = updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNodeChild, oldRouterStateChild, newRouterStateChild, didFindRootLayout, prefetchDataChild, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, newSegmentPathChild, scrollableSegmentsResult);
162
- } else {
163
- // There's no existing Cache Node for this segment. Switch to the
164
- // "create" path.
165
- taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
166
- }
167
- } else {
168
- // This is a new tree. Switch to the "create" path.
169
- taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
299
+ const taskChild = updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNodeChild, oldRouterStateChild, newRouterStateChild, freshness, childDidFindRootLayout, seedDataChild ?? null, seedHeadChild, prefetchDataChild ?? null, prefetchHeadChild, isPrefetchHeadPartialChild, isSamePageNavigation, segmentPath, parallelRouteKey, parentNeedsDynamicRequest || needsDynamicRequest, refreshUrl, accumulation);
300
+ if (taskChild === null) {
301
+ // One of the child tasks discovered a change to the root layout.
302
+ // Immediately unwind from this recursive traversal. This will trigger a
303
+ // full-page navigation.
304
+ return null;
170
305
  }
171
- if (taskChild !== null) {
172
- // Recursively propagate up the child tasks.
173
- if (taskChild.route === null) {
174
- // One of the child tasks discovered a change to the root layout.
175
- // Immediately unwind from this recursive traversal.
176
- return MPA_NAVIGATION_TASK;
177
- }
178
- if (taskChildren === null) {
179
- taskChildren = new Map();
180
- }
181
- taskChildren.set(parallelRouteKey, taskChild);
182
- const newCacheNodeChild = taskChild.node;
183
- if (newCacheNodeChild !== null) {
184
- const newSegmentMapChild = new Map(oldSegmentMapChild);
185
- newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
186
- prefetchParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
187
- }
188
- // The child tree's route state may be different from the prefetched
189
- // route sent by the server. We need to clone it as we traverse back up
190
- // the tree.
191
- const taskChildRoute = taskChild.route;
192
- patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
193
- const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
194
- if (dynamicRequestTreeChild !== null) {
195
- // Something in the child tree is dynamic.
196
- needsDynamicRequest = true;
197
- dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
198
- } else {
199
- dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
200
- }
306
+ // Recursively propagate up the child tasks.
307
+ if (taskChildren === null) {
308
+ taskChildren = new Map();
309
+ }
310
+ taskChildren.set(parallelRouteKey, taskChild);
311
+ const newCacheNodeChild = taskChild.node;
312
+ if (newCacheNodeChild !== null) {
313
+ const newSegmentMapChild = new Map(shouldDropSiblingCaches ? undefined : oldSegmentMapChild);
314
+ newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
315
+ newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
316
+ }
317
+ // The child tree's route state may be different from the prefetched
318
+ // route sent by the server. We need to clone it as we traverse back up
319
+ // the tree.
320
+ const taskChildRoute = taskChild.route;
321
+ patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
322
+ const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
323
+ if (dynamicRequestTreeChild !== null) {
324
+ // Something in the child tree is dynamic.
325
+ childNeedsDynamicRequest = true;
326
+ dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
201
327
  } else {
202
- // The child didn't change. We can use the prefetched router state.
203
- patchedRouterStateChildren[parallelRouteKey] = newRouterStateChild;
204
- dynamicRequestTreeChildren[parallelRouteKey] = newRouterStateChild;
328
+ dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
205
329
  }
206
330
  }
207
- if (taskChildren === null) {
208
- // No new tasks were spawned.
209
- return null;
210
- }
211
- const newCacheNode = {
212
- lazyData: null,
213
- rsc: oldCacheNode.rsc,
214
- // We intentionally aren't updating the prefetchRsc field, since this node
215
- // is already part of the current tree, because it would be weird for
216
- // prefetch data to be newer than the final data. It probably won't ever be
217
- // observable anyway, but it could happen if the segment is unmounted then
218
- // mounted again, because LayoutRouter will momentarily switch to rendering
219
- // prefetchRsc, via useDeferredValue.
220
- prefetchRsc: oldCacheNode.prefetchRsc,
221
- head: oldCacheNode.head,
222
- prefetchHead: oldCacheNode.prefetchHead,
223
- loading: oldCacheNode.loading,
224
- // Everything is cloned except for the children, which we computed above.
225
- parallelRoutes: prefetchParallelRoutes,
226
- navigatedAt
227
- };
228
331
  return {
229
- // Return a cloned copy of the router state with updated children.
332
+ status: needsDynamicRequest ? 0 : 1,
230
333
  route: patchRouterStateWithNewChildren(newRouterState, patchedRouterStateChildren),
231
334
  node: newCacheNode,
232
- dynamicRequestTree: needsDynamicRequest ? patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren) : null,
335
+ dynamicRequestTree: createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest),
336
+ refreshUrl,
233
337
  children: taskChildren
234
338
  };
235
339
  }
236
- function beginRenderingNewRouteTree(navigatedAt, oldRouterState, newRouterState, existingCacheNode, didFindRootLayout, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
237
- if (!didFindRootLayout) {
238
- // The route tree changed before we reached a layout. (The highest-level
239
- // layout in a route tree is referred to as the "root" layout.) This could
240
- // mean that we're navigating between two different root layouts. When this
241
- // happens, we perform a full-page (MPA-style) navigation.
242
- //
243
- // However, the algorithm for deciding where to start rendering a route
244
- // (i.e. the one performed in order to reach this function) is stricter
245
- // than the one used to detect a change in the root layout. So just because
246
- // we're re-rendering a segment outside of the root layout does not mean we
247
- // should trigger a full-page navigation.
248
- //
249
- // Specifically, we handle dynamic parameters differently: two segments are
250
- // considered the same even if their parameter values are different.
251
- //
252
- // Refer to isNavigatingToNewRootLayout for details.
253
- //
254
- // Note that we only have to perform this extra traversal if we didn't
255
- // already discover a root layout in the part of the tree that is unchanged.
256
- // In the common case, this branch is skipped completely.
257
- if (oldRouterState === undefined || (0, _isnavigatingtonewrootlayout.isNavigatingToNewRootLayout)(oldRouterState, newRouterState)) {
258
- // The root layout changed. Perform a full-page navigation.
259
- return MPA_NAVIGATION_TASK;
260
- }
261
- }
262
- return createCacheNodeOnNavigation(navigatedAt, newRouterState, existingCacheNode, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
263
- }
264
- function createCacheNodeOnNavigation(navigatedAt, routerState, existingCacheNode, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
265
- // Same traversal as updateCacheNodeNavigation, but we switch to this path
266
- // once we reach the part of the tree that was not in the previous route. We
267
- // don't need to diff against the old tree, we just need to create a new one.
268
- // The head is assigned to every leaf segment delivered by the server. Based
269
- // on corresponding logic in fill-lazy-items-till-leaf-with-head.ts
270
- const routerStateChildren = routerState[1];
271
- const isLeafSegment = Object.keys(routerStateChildren).length === 0;
272
- // Even we're rendering inside the "new" part of the target tree, we may have
273
- // a locally cached segment that we can reuse. This may come from either 1)
274
- // the CacheNode tree, which lives in React state and is populated by previous
275
- // navigations; or 2) the prefetch cache, which is a separate cache that is
276
- // populated by prefetches.
277
- let rsc;
278
- let loading;
279
- let head;
280
- let cacheNodeNavigatedAt;
281
- if (existingCacheNode !== undefined && // DYNAMIC_STALETIME_MS defaults to 0, but it can be increased using
282
- // the experimental.staleTimes.dynamic config. When set, we'll avoid
283
- // refetching dynamic data if it was fetched within the given threshold.
284
- existingCacheNode.navigatedAt + _navigatereducer.DYNAMIC_STALETIME_MS > navigatedAt) {
285
- // We have an existing CacheNode for this segment, and it's not stale. We
286
- // should reuse it rather than request a new one.
287
- rsc = existingCacheNode.rsc;
288
- loading = existingCacheNode.loading;
289
- head = existingCacheNode.head;
290
- // Don't update the navigatedAt timestamp, since we're reusing stale data.
291
- cacheNodeNavigatedAt = existingCacheNode.navigatedAt;
292
- } else if (prefetchData !== null) {
293
- // There's no existing CacheNode for this segment, but we do have prefetch
294
- // data. If the prefetch data is fully static (i.e. does not contain any
295
- // dynamic holes), we don't need to request it from the server.
296
- rsc = prefetchData[0];
297
- loading = prefetchData[2];
298
- head = isLeafSegment ? possiblyPartialPrefetchHead : null;
299
- // Even though we're accessing the data from the prefetch cache, this is
300
- // conceptually a new segment, not a reused one. So we should update the
301
- // navigatedAt timestamp.
302
- cacheNodeNavigatedAt = navigatedAt;
303
- const isPrefetchRscPartial = prefetchData[3];
304
- if (// Check if the segment data is partial
305
- isPrefetchRscPartial || // Check if the head is partial (only relevant if this is a leaf segment)
306
- isPrefetchHeadPartial && isLeafSegment) {
307
- // We only have partial data from this segment. Like missing segments, we
308
- // must request the full data from the server.
309
- return spawnPendingTask(navigatedAt, routerState, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
310
- } else {
311
- // The prefetch data is fully static, so we can omit it from the
312
- // navigation request.
313
- }
314
- } else {
315
- // There's no prefetch for this segment. Everything from this point will be
316
- // requested from the server, even if there are static children below it.
317
- // Create a terminal task node that will later be fulfilled by
318
- // server response.
319
- return spawnPendingTask(navigatedAt, routerState, null, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
320
- }
321
- // We already have a full segment we can render, so we don't need to request a
322
- // new one from the server. Keep traversing down the tree until we reach
323
- // something that requires a dynamic request.
340
+ function createCacheNodeOnNavigation(navigatedAt, newRouterState, oldCacheNode, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, accumulation) {
341
+ // Same traversal as updateCacheNodeNavigation, but simpler. We switch to this
342
+ // path once we reach the part of the tree that was not in the previous route.
343
+ // We don't need to diff against the old tree, we just need to create a new
344
+ // one. We also don't need to worry about any refresh-related logic.
345
+ //
346
+ // For the most part, this is a subset of updateCacheNodeOnNavigation, so any
347
+ // change that happens in this function likely needs to be applied to that
348
+ // one, too. However there are some places where the behavior intentionally
349
+ // diverges, which is why we keep them separate.
350
+ const newSegment = newRouterState[0];
351
+ const segmentPath = parentParallelRouteKey !== null && parentSegmentPath !== null ? parentSegmentPath.concat([
352
+ parentParallelRouteKey,
353
+ newSegment
354
+ ]) : [];
355
+ const newRouterStateChildren = newRouterState[1];
324
356
  const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
325
- const taskChildren = new Map();
326
- const existingCacheNodeChildren = existingCacheNode !== undefined ? existingCacheNode.parallelRoutes : null;
327
- const cacheNodeChildren = new Map(existingCacheNodeChildren);
328
- let dynamicRequestTreeChildren = {};
329
- let needsDynamicRequest = false;
357
+ const seedDataChildren = seedData !== null ? seedData[1] : null;
358
+ const oldParallelRoutes = oldCacheNode !== undefined ? oldCacheNode.parallelRoutes : undefined;
359
+ let shouldDropSiblingCaches = false;
360
+ let shouldRefreshDynamicData = false;
361
+ let dropPrefetchRsc = false;
362
+ switch(freshness){
363
+ case 0:
364
+ // We should never drop dynamic data in sibling caches except during
365
+ // a refresh.
366
+ shouldDropSiblingCaches = false;
367
+ // Only reuse the dynamic data if experimental.staleTimes.dynamic config
368
+ // is set, and the data is not stale. (This is not a recommended API with
369
+ // Cache Components, but it's supported for backwards compatibility. Use
370
+ // cacheLife instead.)
371
+ //
372
+ // DYNAMIC_STALETIME_MS defaults to 0, but it can be increased.
373
+ shouldRefreshDynamicData = oldCacheNode === undefined || navigatedAt - oldCacheNode.navigatedAt >= _navigatereducer.DYNAMIC_STALETIME_MS;
374
+ dropPrefetchRsc = false;
375
+ break;
376
+ case 1:
377
+ // During hydration, we assume the data sent by the server is both
378
+ // consistent and complete.
379
+ shouldRefreshDynamicData = false;
380
+ shouldDropSiblingCaches = false;
381
+ dropPrefetchRsc = false;
382
+ break;
383
+ case 2:
384
+ // During back/forward navigations, we reuse the dynamic data regardless
385
+ // of how stale it may be.
386
+ shouldRefreshDynamicData = false;
387
+ shouldRefreshDynamicData = false;
388
+ // Only show prefetched data if the dynamic data is still pending. This
389
+ // avoids a flash back to the prefetch state in a case where it's highly
390
+ // likely to have already streamed in.
391
+ //
392
+ // Tehnically, what we're actually checking is whether the dynamic network
393
+ // response was received. But since it's a streaming response, this does
394
+ // not mean that all the dynamic data has fully streamed in. It just means
395
+ // that _some_ of the dynamic data was received. But as a heuristic, we
396
+ // assume that the rest dynamic data will stream in quickly, so it's still
397
+ // better to skip the prefetch state.
398
+ if (oldCacheNode !== undefined) {
399
+ const oldRsc = oldCacheNode.rsc;
400
+ const oldRscDidResolve = !isDeferredRsc(oldRsc) || oldRsc.status !== 'pending';
401
+ dropPrefetchRsc = oldRscDidResolve;
402
+ } else {
403
+ dropPrefetchRsc = false;
404
+ }
405
+ break;
406
+ case 3:
407
+ case 4:
408
+ // Drop all dynamic data.
409
+ shouldRefreshDynamicData = true;
410
+ shouldDropSiblingCaches = true;
411
+ dropPrefetchRsc = false;
412
+ break;
413
+ default:
414
+ freshness;
415
+ break;
416
+ }
417
+ const newParallelRoutes = new Map(shouldDropSiblingCaches ? undefined : oldParallelRoutes);
418
+ const isLeafSegment = Object.keys(newRouterStateChildren).length === 0;
330
419
  if (isLeafSegment) {
331
420
  // The segment path of every leaf segment (i.e. page) is collected into
332
421
  // a result array. This is used by the LayoutRouter to scroll to ensure that
333
422
  // new pages are visible after a navigation.
423
+ //
424
+ // This only happens for new pages, not for refreshed pages.
425
+ //
334
426
  // TODO: We should use a string to represent the segment path instead of
335
427
  // an array. We already use a string representation for the path when
336
428
  // accessing the Segment Cache, so we can use the same one.
337
- scrollableSegmentsResult.push(segmentPath);
429
+ if (accumulation.scrollableSegments === null) {
430
+ accumulation.scrollableSegments = [];
431
+ }
432
+ accumulation.scrollableSegments.push(segmentPath);
433
+ }
434
+ let newCacheNode;
435
+ let needsDynamicRequest;
436
+ if (!shouldRefreshDynamicData && oldCacheNode !== undefined) {
437
+ // Reuse the existing CacheNode
438
+ newCacheNode = reuseDynamicCacheNode(dropPrefetchRsc, oldCacheNode, newParallelRoutes);
439
+ needsDynamicRequest = false;
440
+ } else if (seedData !== null && seedData[0] !== null) {
441
+ // If this navigation was the result of an action, then check if the
442
+ // server sent back data in the action response. We should favor using
443
+ // that, rather than performing a separate request. This is both better
444
+ // for performance and it's more likely to be consistent with any
445
+ // writes that were just performed by the action, compared to a
446
+ // separate request.
447
+ const seedRsc = seedData[0];
448
+ const seedLoading = seedData[2];
449
+ const isSeedRscPartial = false;
450
+ const isSeedHeadPartial = seedHead === null && freshness !== 1;
451
+ newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
452
+ needsDynamicRequest = isLeafSegment && isSeedHeadPartial;
453
+ } else if (freshness === 1 && isLeafSegment && seedHead !== null) {
454
+ // This is another weird case related to "not found" pages and hydration.
455
+ // There will be a head sent by the server, but no page seed data.
456
+ // TODO: We really should get rid of all these "not found" specific quirks
457
+ // and make sure the tree is always consistent.
458
+ const seedRsc = null;
459
+ const seedLoading = null;
460
+ const isSeedRscPartial = false;
461
+ const isSeedHeadPartial = false;
462
+ newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
463
+ needsDynamicRequest = false;
464
+ } else if (freshness !== 1 && prefetchData !== null) {
465
+ // Consult the prefetch cache.
466
+ const prefetchRsc = prefetchData[0];
467
+ const prefetchLoading = prefetchData[2];
468
+ const isPrefetchRSCPartial = prefetchData[3];
469
+ newCacheNode = readCacheNodeFromSeedData(prefetchRsc, prefetchLoading, isPrefetchRSCPartial, prefetchHead, isPrefetchHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
470
+ needsDynamicRequest = isPrefetchRSCPartial || isLeafSegment && isPrefetchHeadPartial;
338
471
  } else {
339
- for(let parallelRouteKey in routerStateChildren){
340
- const routerStateChild = routerStateChildren[parallelRouteKey];
341
- const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
342
- const existingSegmentMapChild = existingCacheNodeChildren !== null ? existingCacheNodeChildren.get(parallelRouteKey) : undefined;
343
- const segmentChild = routerStateChild[0];
344
- const segmentPathChild = segmentPath.concat([
345
- parallelRouteKey,
346
- segmentChild
347
- ]);
348
- const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
349
- const existingCacheNodeChild = existingSegmentMapChild !== undefined ? existingSegmentMapChild.get(segmentKeyChild) : undefined;
350
- const taskChild = createCacheNodeOnNavigation(navigatedAt, routerStateChild, existingCacheNodeChild, prefetchDataChild, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPathChild, scrollableSegmentsResult);
351
- taskChildren.set(parallelRouteKey, taskChild);
352
- const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
353
- if (dynamicRequestTreeChild !== null) {
354
- // Something in the child tree is dynamic.
355
- needsDynamicRequest = true;
356
- dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
357
- } else {
358
- dynamicRequestTreeChildren[parallelRouteKey] = routerStateChild;
359
- }
360
- const newCacheNodeChild = taskChild.node;
361
- if (newCacheNodeChild !== null) {
362
- const newSegmentMapChild = new Map();
363
- newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
364
- cacheNodeChildren.set(parallelRouteKey, newSegmentMapChild);
365
- }
472
+ // Spawn a request to fetch new data from the server.
473
+ newCacheNode = spawnNewCacheNode(newParallelRoutes, isLeafSegment, navigatedAt, freshness);
474
+ needsDynamicRequest = true;
475
+ }
476
+ let patchedRouterStateChildren = {};
477
+ let taskChildren = null;
478
+ let childNeedsDynamicRequest = false;
479
+ let dynamicRequestTreeChildren = {};
480
+ for(let parallelRouteKey in newRouterStateChildren){
481
+ const newRouterStateChild = newRouterStateChildren[parallelRouteKey];
482
+ const oldSegmentMapChild = oldParallelRoutes !== undefined ? oldParallelRoutes.get(parallelRouteKey) : undefined;
483
+ const seedDataChild = seedDataChildren !== null ? seedDataChildren[parallelRouteKey] : null;
484
+ const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
485
+ const newSegmentChild = newRouterStateChild[0];
486
+ const newSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(newSegmentChild);
487
+ const oldCacheNodeChild = oldSegmentMapChild !== undefined ? oldSegmentMapChild.get(newSegmentKeyChild) : undefined;
488
+ const taskChild = createCacheNodeOnNavigation(navigatedAt, newRouterStateChild, oldCacheNodeChild, freshness, seedDataChild ?? null, seedHead, prefetchDataChild ?? null, prefetchHead, isPrefetchHeadPartial, segmentPath, parallelRouteKey, parentNeedsDynamicRequest || needsDynamicRequest, accumulation);
489
+ if (taskChildren === null) {
490
+ taskChildren = new Map();
491
+ }
492
+ taskChildren.set(parallelRouteKey, taskChild);
493
+ const newCacheNodeChild = taskChild.node;
494
+ if (newCacheNodeChild !== null) {
495
+ const newSegmentMapChild = new Map(shouldDropSiblingCaches ? undefined : oldSegmentMapChild);
496
+ newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
497
+ newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
498
+ }
499
+ const taskChildRoute = taskChild.route;
500
+ patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
501
+ const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
502
+ if (dynamicRequestTreeChild !== null) {
503
+ childNeedsDynamicRequest = true;
504
+ dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
505
+ } else {
506
+ dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
366
507
  }
367
508
  }
368
509
  return {
369
- // Since we're inside a new route tree, unlike the
370
- // `updateCacheNodeOnNavigation` path, the router state on the children
371
- // tasks is always the same as the router state we pass in. So we don't need
372
- // to clone/modify it.
373
- route: routerState,
374
- node: {
375
- lazyData: null,
376
- // Since this segment is already full, we don't need to use the
377
- // `prefetchRsc` field.
378
- rsc,
379
- prefetchRsc: null,
380
- head,
381
- prefetchHead: null,
382
- loading,
383
- parallelRoutes: cacheNodeChildren,
384
- navigatedAt: cacheNodeNavigatedAt
385
- },
386
- dynamicRequestTree: needsDynamicRequest ? patchRouterStateWithNewChildren(routerState, dynamicRequestTreeChildren) : null,
510
+ status: needsDynamicRequest ? 0 : 1,
511
+ route: patchRouterStateWithNewChildren(newRouterState, patchedRouterStateChildren),
512
+ node: newCacheNode,
513
+ dynamicRequestTree: createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest),
514
+ // This route is not part of the current tree, so there's no reason to
515
+ // track the refresh URL.
516
+ refreshUrl: null,
387
517
  children: taskChildren
388
518
  };
389
519
  }
@@ -406,22 +536,48 @@ function patchRouterStateWithNewChildren(baseRouterState, newChildren) {
406
536
  }
407
537
  return clone;
408
538
  }
409
- function spawnPendingTask(navigatedAt, routerState, prefetchData, prefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
410
- // Create a task that will later be fulfilled by data from the server.
411
- // Clone the prefetched route tree and the `refetch` marker to it. We'll send
412
- // this to the server so it knows where to start rendering.
413
- const dynamicRequestTree = patchRouterStateWithNewChildren(routerState, routerState[1]);
414
- dynamicRequestTree[3] = 'refetch';
415
- const newTask = {
416
- route: routerState,
417
- // Corresponds to the part of the route that will be rendered on the server.
418
- node: createPendingCacheNode(navigatedAt, routerState, prefetchData, prefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult),
419
- // Because this is non-null, and it gets propagated up through the parent
420
- // tasks, the root task will know that it needs to perform a server request.
421
- dynamicRequestTree,
422
- children: null
423
- };
424
- return newTask;
539
+ function createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest) {
540
+ // Create a FlightRouterState that instructs the server how to render the
541
+ // requested segment.
542
+ //
543
+ // Or, if neither this segment nor any of the children require a new data,
544
+ // then we return `null` to skip the request.
545
+ let dynamicRequestTree = null;
546
+ if (needsDynamicRequest) {
547
+ dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
548
+ // The "refetch" marker is set on the top-most segment that requires new
549
+ // data. We can omit it if a parent was already marked.
550
+ if (!parentNeedsDynamicRequest) {
551
+ dynamicRequestTree[3] = 'refetch';
552
+ }
553
+ } else if (childNeedsDynamicRequest) {
554
+ // This segment does not request new data, but at least one of its
555
+ // children does.
556
+ dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
557
+ } else {
558
+ dynamicRequestTree = null;
559
+ }
560
+ return dynamicRequestTree;
561
+ }
562
+ function accumulateRefreshUrl(accumulation, refreshUrl) {
563
+ // This is a refresh navigation, and we're inside a "default" slot that's
564
+ // not part of the current route; it was reused from an older route. In
565
+ // order to get fresh data for this reused route, we need to issue a
566
+ // separate request using the old route's URL.
567
+ //
568
+ // Track these extra URLs in the accumulated result. Later, we'll construct
569
+ // an appropriate request for each unique URL in the final set. The reason
570
+ // we don't do it immediately here is so we can deduplicate multiple
571
+ // instances of the same URL into a single request. See
572
+ // listenForDynamicRequest for more details.
573
+ const separateRefreshUrls = accumulation.separateRefreshUrls;
574
+ if (separateRefreshUrls === null) {
575
+ accumulation.separateRefreshUrls = new Set([
576
+ refreshUrl
577
+ ]);
578
+ } else {
579
+ separateRefreshUrls.add(refreshUrl);
580
+ }
425
581
  }
426
582
  function reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterState) {
427
583
  // This is a "default" segment. These are never sent by the server during a
@@ -446,166 +602,349 @@ function reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterState) {
446
602
  reusedRouterState[2] = (0, _createhreffromurl.createHrefFromUrl)(oldUrl);
447
603
  reusedRouterState[3] = 'refresh';
448
604
  }
449
- return {
450
- route: reusedRouterState,
451
- node: null,
452
- dynamicRequestTree: null,
453
- children: null
605
+ return reusedRouterState;
606
+ }
607
+ function reuseDynamicCacheNode(dropPrefetchRsc, existingCacheNode, parallelRoutes) {
608
+ // Clone an existing CacheNode's data, with (possibly) new children.
609
+ const cacheNode = {
610
+ rsc: existingCacheNode.rsc,
611
+ prefetchRsc: dropPrefetchRsc ? null : existingCacheNode.prefetchRsc,
612
+ head: existingCacheNode.head,
613
+ prefetchHead: dropPrefetchRsc ? null : existingCacheNode.prefetchHead,
614
+ loading: existingCacheNode.loading,
615
+ parallelRoutes,
616
+ // Don't update the navigatedAt timestamp, since we're reusing
617
+ // existing data.
618
+ navigatedAt: existingCacheNode.navigatedAt
454
619
  };
620
+ return cacheNode;
455
621
  }
456
- function listenForDynamicRequest(task, responsePromise) {
457
- responsePromise.then((result)=>{
458
- if (typeof result === 'string') {
459
- // Happens when navigating to page in `pages` from `app`. We shouldn't
460
- // get here because should have already handled this during
461
- // the prefetch.
462
- return;
622
+ function readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isPageSegment, parallelRoutes, navigatedAt) {
623
+ // TODO: Currently this is threaded through the navigation logic using the
624
+ // CacheNodeSeedData type, but in the future this will read directly from
625
+ // the Segment Cache. See readRenderSnapshotFromCache.
626
+ let rsc;
627
+ let prefetchRsc;
628
+ if (isSeedRscPartial) {
629
+ // The prefetched data contains dynamic holes. Create a pending promise that
630
+ // will be fulfilled when the dynamic data is received from the server.
631
+ prefetchRsc = seedRsc;
632
+ rsc = createDeferredRsc();
633
+ } else {
634
+ // The prefetched data is complete. Use it directly.
635
+ prefetchRsc = null;
636
+ rsc = seedRsc;
637
+ }
638
+ // If this is a page segment, also read the head.
639
+ let prefetchHead;
640
+ let head;
641
+ if (isPageSegment) {
642
+ if (isSeedHeadPartial) {
643
+ prefetchHead = seedHead;
644
+ head = createDeferredRsc();
645
+ } else {
646
+ prefetchHead = null;
647
+ head = seedHead;
463
648
  }
464
- const { flightData, debugInfo } = result;
465
- for (const normalizedFlightData of flightData){
466
- const { segmentPath, tree: serverRouterState, seedData: dynamicData, head: dynamicHead } = normalizedFlightData;
467
- if (!dynamicData) {
649
+ } else {
650
+ prefetchHead = null;
651
+ head = null;
652
+ }
653
+ const cacheNode = {
654
+ rsc,
655
+ prefetchRsc,
656
+ head,
657
+ prefetchHead,
658
+ // TODO: Technically, a loading boundary could contain dynamic data. We
659
+ // should have separate `loading` and `prefetchLoading` fields to handle
660
+ // this, like we do for the segment data and head.
661
+ loading: seedLoading,
662
+ parallelRoutes,
663
+ navigatedAt
664
+ };
665
+ return cacheNode;
666
+ }
667
+ function spawnNewCacheNode(parallelRoutes, isLeafSegment, navigatedAt, freshness) {
668
+ // We should never spawn network requests during hydration. We must treat the
669
+ // initial payload as authoritative, because the initial page load is used
670
+ // as a last-ditch mechanism for recovering the app.
671
+ //
672
+ // This is also an important safety check because if this leaks into the
673
+ // server rendering path (which theoretically it never should because
674
+ // the server payload should be consistent), the server would hang because
675
+ // these promises would never resolve.
676
+ //
677
+ // TODO: There is an existing case where the global "not found" boundary
678
+ // triggers this path. But it does render correctly despite that. That's an
679
+ // unusual render path so it's not surprising, but we should look into
680
+ // modeling it in a more consistent way. See also the /_notFound special
681
+ // case in updateCacheNodeOnNavigation.
682
+ const isHydration = freshness === 1;
683
+ const cacheNode = {
684
+ rsc: !isHydration ? createDeferredRsc() : null,
685
+ prefetchRsc: null,
686
+ head: !isHydration && isLeafSegment ? createDeferredRsc() : null,
687
+ prefetchHead: null,
688
+ loading: !isHydration ? createDeferredRsc() : null,
689
+ parallelRoutes,
690
+ navigatedAt
691
+ };
692
+ return cacheNode;
693
+ }
694
+ // Represents whether the previuos navigation resulted in a route tree mismatch.
695
+ // A mismatch results in a refresh of the page. If there are two successive
696
+ // mismatches, we will fall back to an MPA navigation, to prevent a retry loop.
697
+ let previousNavigationDidMismatch = false;
698
+ function spawnDynamicRequests(task, primaryUrl, nextUrl, freshnessPolicy, accumulation) {
699
+ const dynamicRequestTree = task.dynamicRequestTree;
700
+ if (dynamicRequestTree === null) {
701
+ // This navigation was fully cached. There are no dynamic requests to spawn.
702
+ previousNavigationDidMismatch = false;
703
+ return;
704
+ }
705
+ // This is intentionally not an async function to discourage the caller from
706
+ // awaiting the result. Any subsequent async operations spawned by this
707
+ // function should result in a separate navigation task, rather than
708
+ // block the original one.
709
+ //
710
+ // In this function we spawn (but do not await) all the network requests that
711
+ // block the navigation, and collect the promises. The next function,
712
+ // `finishNavigationTask`, can await the promises in any order without
713
+ // accidentally introducing a network waterfall.
714
+ const primaryRequestPromise = fetchMissingDynamicData(task, dynamicRequestTree, primaryUrl, nextUrl, freshnessPolicy);
715
+ const separateRefreshUrls = accumulation.separateRefreshUrls;
716
+ let refreshRequestPromises = null;
717
+ if (separateRefreshUrls !== null) {
718
+ // There are multiple URLs that we need to request the data from. This
719
+ // happens when a "default" parallel route slot is present in the tree, and
720
+ // its data cannot be fetched from the current route. We need to split the
721
+ // combined dynamic request tree into separate requests per URL.
722
+ // TODO: Create a scoped dynamic request tree that omits anything that
723
+ // is not relevant to the given URL. Without doing this, the server may
724
+ // sometimes render more data than necessary; this is not a regression
725
+ // compared to the pre-Segment Cache implementation, though, just an
726
+ // optimization we can make in the future.
727
+ // Construct a request tree for each additional refresh URL. This will
728
+ // prune away everything except the parts of the tree that match the
729
+ // given refresh URL.
730
+ refreshRequestPromises = [];
731
+ const canonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(primaryUrl);
732
+ for (const refreshUrl of separateRefreshUrls){
733
+ if (refreshUrl === canonicalUrl) {
468
734
  continue;
469
735
  }
470
- writeDynamicDataIntoPendingTask(task, segmentPath, serverRouterState, dynamicData, dynamicHead, debugInfo);
736
+ // TODO: Create a scoped dynamic request tree that omits anything that
737
+ // is not relevant to the given URL. Without doing this, the server may
738
+ // sometimes render more data than necessary; this is not a regression
739
+ // compared to the pre-Segment Cache implementation, though, just an
740
+ // optimization we can make in the future.
741
+ // const scopedDynamicRequestTree = splitTaskByURL(task, refreshUrl)
742
+ const scopedDynamicRequestTree = dynamicRequestTree;
743
+ if (scopedDynamicRequestTree !== null) {
744
+ refreshRequestPromises.push(fetchMissingDynamicData(task, scopedDynamicRequestTree, new URL(refreshUrl, location.origin), // TODO: Just noticed that this should actually the Next-Url at the
745
+ // time the refresh URL was set, not the current Next-Url. Need to
746
+ // start tracking this alongside the refresh URL. In the meantime,
747
+ // if a refresh fails due to a mismatch, it will trigger a
748
+ // hard refresh.
749
+ nextUrl, freshnessPolicy));
750
+ }
471
751
  }
472
- // Now that we've exhausted all the data we received from the server, if
473
- // there are any remaining pending tasks in the tree, abort them now.
474
- // If there's any missing data, it will trigger a lazy fetch.
475
- abortTask(task, null, debugInfo);
476
- }, (error)=>{
477
- // This will trigger an error during render
478
- abortTask(task, error, null);
479
- });
752
+ }
753
+ // Further async operations are moved into this separate function to
754
+ // discourage sequential network requests.
755
+ const voidPromise = finishNavigationTask(task, nextUrl, primaryRequestPromise, refreshRequestPromises);
756
+ // `finishNavigationTask` is responsible for error handling, so we can attach
757
+ // noop callbacks to this promise.
758
+ voidPromise.then(noop, noop);
480
759
  }
481
- function writeDynamicDataIntoPendingTask(rootTask, segmentPath, serverRouterState, dynamicData, dynamicHead, debugInfo) {
482
- // The data sent by the server represents only a subtree of the app. We need
483
- // to find the part of the task tree that matches the server response, and
484
- // fulfill it using the dynamic data.
485
- //
486
- // segmentPath represents the parent path of subtree. It's a repeating pattern
487
- // of parallel route key and segment:
760
+ async function finishNavigationTask(task, nextUrl, primaryRequestPromise, refreshRequestPromises) {
761
+ // Wait for all the requests to finish, or for the first one to fail.
762
+ let exitStatus = await waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises);
763
+ // Once the all the requests have finished, check the tree for any remaining
764
+ // pending tasks. If anything is still pending, it means the server response
765
+ // does not match the client, and we must refresh to get back to a consistent
766
+ // state. We can skip this step if we already detected a mismatch during the
767
+ // first phase; it doesn't matter in that case because we're going to refresh
768
+ // the whole tree regardless.
769
+ if (exitStatus === 0) {
770
+ exitStatus = abortRemainingPendingTasks(task, null, null);
771
+ }
772
+ switch(exitStatus){
773
+ case 0:
774
+ {
775
+ // The task has completely finished. There's no missing data. Exit.
776
+ previousNavigationDidMismatch = false;
777
+ return;
778
+ }
779
+ case 1:
780
+ {
781
+ // Some data failed to finish loading. Trigger a soft retry.
782
+ // TODO: As an extra precaution against soft retry loops, consider
783
+ // tracking whether a navigation was itself triggered by a retry. If two
784
+ // happen in a row, fall back to a hard retry.
785
+ const isHardRetry = false;
786
+ const primaryRequestResult = await primaryRequestPromise;
787
+ dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, task.route);
788
+ return;
789
+ }
790
+ case 2:
791
+ {
792
+ // Some data failed to finish loading in a non-recoverable way, such as a
793
+ // network error. Trigger an MPA navigation.
794
+ //
795
+ // Hard navigating/refreshing is how we prevent an infinite retry loop
796
+ // caused by a network error — when the network fails, we fall back to the
797
+ // browser behavior for offline navigations. In the future, Next.js may
798
+ // introduce its own custom handling of offline navigations, but that
799
+ // doesn't exist yet.
800
+ const isHardRetry = true;
801
+ const primaryRequestResult = await primaryRequestPromise;
802
+ dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, task.route);
803
+ return;
804
+ }
805
+ default:
806
+ {
807
+ return exitStatus;
808
+ }
809
+ }
810
+ }
811
+ function waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises) {
812
+ // Custom async combinator logic. This could be replaced by Promise.any but
813
+ // we don't assume that's available.
488
814
  //
489
- // [string, Segment, string, Segment, string, Segment, ...]
815
+ // Each promise resolves once the server responsds and the data is written
816
+ // into the CacheNode tree. Resolve the combined promise once all the
817
+ // requests finish.
490
818
  //
491
- // Iterate through the path and finish any tasks that match this payload.
492
- let task = rootTask;
493
- for(let i = 0; i < segmentPath.length; i += 2){
494
- const parallelRouteKey = segmentPath[i];
495
- const segment = segmentPath[i + 1];
496
- const taskChildren = task.children;
497
- if (taskChildren !== null) {
498
- const taskChild = taskChildren.get(parallelRouteKey);
499
- if (taskChild !== undefined) {
500
- const taskSegment = taskChild.route[0];
501
- if ((0, _matchsegments.matchSegment)(segment, taskSegment)) {
502
- // Found a match for this task. Keep traversing down the task tree.
503
- task = taskChild;
504
- continue;
819
+ // Or, resolve as soon as one of the requests fails, without waiting for the
820
+ // others to finish.
821
+ return new Promise((resolve)=>{
822
+ const onFulfill = (result)=>{
823
+ if (result.exitStatus === 0) {
824
+ remainingCount--;
825
+ if (remainingCount === 0) {
826
+ // All the requests finished successfully.
827
+ resolve(0);
505
828
  }
829
+ } else {
830
+ // One of the requests failed. Exit with a failing status.
831
+ // NOTE: It's possible for one of the requests to fail with SoftRetry
832
+ // and a later one to fail with HardRetry. In this case, we choose to
833
+ // retry immediately, rather than delay the retry until all the requests
834
+ // finish. If it fails again, we will hard retry on the next
835
+ // attempt, anyway.
836
+ resolve(result.exitStatus);
506
837
  }
838
+ };
839
+ // onReject shouldn't ever be called because fetchMissingDynamicData's
840
+ // entire body is wrapped in a try/catch. This is just defensive.
841
+ const onReject = ()=>resolve(2);
842
+ // Attach the listeners to the promises.
843
+ let remainingCount = 1;
844
+ primaryRequestPromise.then(onFulfill, onReject);
845
+ if (refreshRequestPromises !== null) {
846
+ remainingCount += refreshRequestPromises.length;
847
+ refreshRequestPromises.forEach((refreshRequestPromise)=>refreshRequestPromise.then(onFulfill, onReject));
507
848
  }
508
- // We didn't find a child task that matches the server data. Exit. We won't
509
- // abort the task, though, because a different FlightDataPath may be able to
510
- // fulfill it (see loop in listenForDynamicRequest). We only abort tasks
511
- // once we've run out of data.
512
- return;
849
+ });
850
+ }
851
+ function dispatchRetryDueToTreeMismatch(isHardRetry, retryUrl, retryNextUrl, seed, baseTree) {
852
+ // If this is the second time in a row that a navigation resulted in a
853
+ // mismatch, fall back to a hard (MPA) refresh.
854
+ isHardRetry = isHardRetry || previousNavigationDidMismatch;
855
+ previousNavigationDidMismatch = true;
856
+ const retryAction = {
857
+ type: _routerreducertypes.ACTION_SERVER_PATCH,
858
+ previousTree: baseTree,
859
+ url: retryUrl,
860
+ nextUrl: retryNextUrl,
861
+ seed,
862
+ mpa: isHardRetry
863
+ };
864
+ (0, _useactionqueue.dispatchAppRouterAction)(retryAction);
865
+ }
866
+ async function fetchMissingDynamicData(task, dynamicRequestTree, url, nextUrl, freshnessPolicy) {
867
+ try {
868
+ const result = await (0, _fetchserverresponse.fetchServerResponse)(url, {
869
+ flightRouterState: dynamicRequestTree,
870
+ nextUrl,
871
+ isHmrRefresh: freshnessPolicy === 4
872
+ });
873
+ if (typeof result === 'string') {
874
+ // fetchServerResponse will return an href to indicate that the SPA
875
+ // navigation failed. For example, if the server triggered a hard
876
+ // redirect, or the fetch request errored. Initiate an MPA navigation
877
+ // to the given href.
878
+ return {
879
+ exitStatus: 2,
880
+ url: new URL(result, location.origin),
881
+ seed: null
882
+ };
883
+ }
884
+ const seed = (0, _navigation.convertServerPatchToFullTree)(task.route, result.flightData, result.renderedSearch);
885
+ const didReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(task, seed.tree, seed.data, seed.head, result.debugInfo);
886
+ return {
887
+ exitStatus: didReceiveUnknownParallelRoute ? 1 : 0,
888
+ url: new URL(result.canonicalUrl, location.origin),
889
+ seed
890
+ };
891
+ } catch {
892
+ // This shouldn't happen because fetchServerResponse's entire body is
893
+ // wrapped in a try/catch. If it does, though, it implies the server failed
894
+ // to respond with any tree at all. So we must fall back to a hard retry.
895
+ return {
896
+ exitStatus: 2,
897
+ url: url,
898
+ seed: null
899
+ };
513
900
  }
514
- finishTaskUsingDynamicDataPayload(task, serverRouterState, dynamicData, dynamicHead, debugInfo);
515
901
  }
516
- function finishTaskUsingDynamicDataPayload(task, serverRouterState, dynamicData, dynamicHead, debugInfo) {
517
- if (task.dynamicRequestTree === null) {
518
- // Everything in this subtree is already complete. Bail out.
519
- return;
902
+ function writeDynamicDataIntoNavigationTask(task, serverRouterState, dynamicData, dynamicHead, debugInfo) {
903
+ if (task.status === 0 && dynamicData !== null) {
904
+ task.status = 1;
905
+ finishPendingCacheNode(task.node, dynamicData, dynamicHead, debugInfo);
520
906
  }
521
- // dynamicData may represent a larger subtree than the task. Before we can
522
- // finish the task, we need to line them up.
523
907
  const taskChildren = task.children;
524
- const taskNode = task.node;
525
- if (taskChildren === null) {
526
- // We've reached the leaf node of the pending task. The server data tree
527
- // lines up the pending Cache Node tree. We can now switch to the
528
- // normal algorithm.
529
- if (taskNode !== null) {
530
- finishPendingCacheNode(taskNode, task.route, serverRouterState, dynamicData, dynamicHead, debugInfo);
531
- // Set this to null to indicate that this task is now complete.
532
- task.dynamicRequestTree = null;
533
- }
534
- return;
535
- }
536
- // The server returned more data than we need to finish the task. Skip over
537
- // the extra segments until we reach the leaf task node.
538
908
  const serverChildren = serverRouterState[1];
539
- const dynamicDataChildren = dynamicData[1];
540
- for(const parallelRouteKey in serverRouterState){
541
- const serverRouterStateChild = serverChildren[parallelRouteKey];
542
- const dynamicDataChild = dynamicDataChildren[parallelRouteKey];
543
- const taskChild = taskChildren.get(parallelRouteKey);
544
- if (taskChild !== undefined) {
545
- const taskSegment = taskChild.route[0];
546
- if ((0, _matchsegments.matchSegment)(serverRouterStateChild[0], taskSegment) && dynamicDataChild !== null && dynamicDataChild !== undefined) {
547
- // Found a match for this task. Keep traversing down the task tree.
548
- return finishTaskUsingDynamicDataPayload(taskChild, serverRouterStateChild, dynamicDataChild, dynamicHead, debugInfo);
909
+ const dynamicDataChildren = dynamicData !== null ? dynamicData[1] : null;
910
+ // Detect whether the server sends a parallel route slot that the client
911
+ // doesn't know about.
912
+ let didReceiveUnknownParallelRoute = false;
913
+ if (taskChildren !== null) {
914
+ for(const parallelRouteKey in serverChildren){
915
+ const serverRouterStateChild = serverChildren[parallelRouteKey];
916
+ const dynamicDataChild = dynamicDataChildren !== null ? dynamicDataChildren[parallelRouteKey] : null;
917
+ const taskChild = taskChildren.get(parallelRouteKey);
918
+ if (taskChild === undefined) {
919
+ // The server sent a child segment that the client doesn't know about.
920
+ //
921
+ // When we receive an unknown parallel route, we must consider it a
922
+ // mismatch. This is unlike the case where the segment itself
923
+ // mismatches, because multiple routes can be active simultaneously.
924
+ // But a given layout should never have a mismatching set of
925
+ // child slots.
926
+ //
927
+ // Theoretically, this should only happen in development during an HMR
928
+ // refresh, because the set of parallel routes for a layout does not
929
+ // change over the lifetime of a build/deployment. In production, we
930
+ // should have already mismatched on either the build id or the segment
931
+ // path. But as an extra precaution, we validate in prod, too.
932
+ didReceiveUnknownParallelRoute = true;
933
+ } else {
934
+ const taskSegment = taskChild.route[0];
935
+ if ((0, _matchsegments.matchSegment)(serverRouterStateChild[0], taskSegment) && dynamicDataChild !== null && dynamicDataChild !== undefined) {
936
+ // Found a match for this task. Keep traversing down the task tree.
937
+ const childDidReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(taskChild, serverRouterStateChild, dynamicDataChild, dynamicHead, debugInfo);
938
+ if (childDidReceiveUnknownParallelRoute) {
939
+ didReceiveUnknownParallelRoute = true;
940
+ }
941
+ }
549
942
  }
550
943
  }
551
- // We didn't find a child task that matches the server data. We won't abort
552
- // the task, though, because a different FlightDataPath may be able to
553
- // fulfill it (see loop in listenForDynamicRequest). We only abort tasks
554
- // once we've run out of data.
555
- }
556
- }
557
- function createPendingCacheNode(navigatedAt, routerState, prefetchData, prefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
558
- const routerStateChildren = routerState[1];
559
- const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
560
- const parallelRoutes = new Map();
561
- for(let parallelRouteKey in routerStateChildren){
562
- const routerStateChild = routerStateChildren[parallelRouteKey];
563
- const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
564
- const segmentChild = routerStateChild[0];
565
- const segmentPathChild = segmentPath.concat([
566
- parallelRouteKey,
567
- segmentChild
568
- ]);
569
- const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
570
- const newCacheNodeChild = createPendingCacheNode(navigatedAt, routerStateChild, prefetchDataChild === undefined ? null : prefetchDataChild, prefetchHead, isPrefetchHeadPartial, segmentPathChild, scrollableSegmentsResult);
571
- const newSegmentMapChild = new Map();
572
- newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
573
- parallelRoutes.set(parallelRouteKey, newSegmentMapChild);
574
- }
575
- // The head is assigned to every leaf segment delivered by the server. Based
576
- // on corresponding logic in fill-lazy-items-till-leaf-with-head.ts
577
- const isLeafSegment = parallelRoutes.size === 0;
578
- if (isLeafSegment) {
579
- // The segment path of every leaf segment (i.e. page) is collected into
580
- // a result array. This is used by the LayoutRouter to scroll to ensure that
581
- // new pages are visible after a navigation.
582
- // TODO: We should use a string to represent the segment path instead of
583
- // an array. We already use a string representation for the path when
584
- // accessing the Segment Cache, so we can use the same one.
585
- scrollableSegmentsResult.push(segmentPath);
586
944
  }
587
- const maybePrefetchRsc = prefetchData !== null ? prefetchData[0] : null;
588
- return {
589
- lazyData: null,
590
- parallelRoutes: parallelRoutes,
591
- prefetchRsc: maybePrefetchRsc !== undefined ? maybePrefetchRsc : null,
592
- prefetchHead: isLeafSegment ? prefetchHead : [
593
- null,
594
- null
595
- ],
596
- // Create a deferred promise. This will be fulfilled once the dynamic
597
- // response is received from the server.
598
- rsc: createDeferredRsc(),
599
- head: isLeafSegment ? createDeferredRsc() : null,
600
- // TODO: Technically, a loading boundary could contain dynamic data. We must
601
- // have separate `loading` and `prefetchLoading` fields to handle this, like
602
- // we do for the segment data and head.
603
- loading: prefetchData !== null ? prefetchData[2] ?? null : // We'll fulfill it based on the dynamic response, just like `rsc` and `head`.
604
- createDeferredRsc(),
605
- navigatedAt
606
- };
945
+ return didReceiveUnknownParallelRoute;
607
946
  }
608
- function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData, dynamicHead, debugInfo) {
947
+ function finishPendingCacheNode(cacheNode, dynamicData, dynamicHead, debugInfo) {
609
948
  // Writes a dynamic response into an existing Cache Node tree. This does _not_
610
949
  // create a new tree, it updates the existing tree in-place. So it must follow
611
950
  // the Suspense rules of cache safety — it can resolve pending promises, but
@@ -616,49 +955,16 @@ function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData,
616
955
  // We must resolve every promise in the tree, or else it will suspend
617
956
  // indefinitely. If we did not receive data for a segment, we will resolve its
618
957
  // data promise to `null` to trigger a lazy fetch during render.
619
- const taskStateChildren = taskState[1];
620
- const serverStateChildren = serverState[1];
621
- const dataChildren = dynamicData[1];
622
- // The router state that we traverse the tree with (taskState) is the same one
623
- // that we used to construct the pending Cache Node tree. That way we're sure
624
- // to resolve all the pending promises.
625
- const parallelRoutes = cacheNode.parallelRoutes;
626
- for(let parallelRouteKey in taskStateChildren){
627
- const taskStateChild = taskStateChildren[parallelRouteKey];
628
- const serverStateChild = serverStateChildren[parallelRouteKey];
629
- const dataChild = dataChildren[parallelRouteKey];
630
- const segmentMapChild = parallelRoutes.get(parallelRouteKey);
631
- const taskSegmentChild = taskStateChild[0];
632
- const taskSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(taskSegmentChild);
633
- const cacheNodeChild = segmentMapChild !== undefined ? segmentMapChild.get(taskSegmentKeyChild) : undefined;
634
- if (cacheNodeChild !== undefined) {
635
- if (serverStateChild !== undefined && (0, _matchsegments.matchSegment)(taskSegmentChild, serverStateChild[0])) {
636
- if (dataChild !== undefined && dataChild !== null) {
637
- // This is the happy path. Recursively update all the children.
638
- finishPendingCacheNode(cacheNodeChild, taskStateChild, serverStateChild, dataChild, dynamicHead, debugInfo);
639
- } else {
640
- // The server never returned data for this segment. Trigger a lazy
641
- // fetch during render. This shouldn't happen because the Route Tree
642
- // and the Seed Data tree sent by the server should always be the same
643
- // shape when part of the same server response.
644
- abortPendingCacheNode(taskStateChild, cacheNodeChild, null, debugInfo);
645
- }
646
- } else {
647
- // The server never returned data for this segment. Trigger a lazy
648
- // fetch during render.
649
- abortPendingCacheNode(taskStateChild, cacheNodeChild, null, debugInfo);
650
- }
651
- } else {
652
- // The server response matches what was expected to receive, but there's
653
- // no matching Cache Node in the task tree. This is a bug in the
654
- // implementation because we should have created a node for every
655
- // segment in the tree that's associated with this task.
656
- }
657
- }
658
958
  // Use the dynamic data from the server to fulfill the deferred RSC promise
659
959
  // on the Cache Node.
660
960
  const rsc = cacheNode.rsc;
661
961
  const dynamicSegmentData = dynamicData[0];
962
+ if (dynamicSegmentData === null) {
963
+ // This is an empty CacheNode; this particular server request did not
964
+ // render this segment. There may be a separate pending request that will,
965
+ // though, so we won't abort the task until all pending requests finish.
966
+ return;
967
+ }
662
968
  if (rsc === null) {
663
969
  // This is a lazy cache node. We can overwrite it. This is only safe
664
970
  // because we know that the LayoutRouter suspends if `rsc` is `null`.
@@ -687,51 +993,56 @@ function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData,
687
993
  head.resolve(dynamicHead, debugInfo);
688
994
  }
689
995
  }
690
- function abortTask(task, error, debugInfo) {
691
- const cacheNode = task.node;
692
- if (cacheNode === null) {
693
- // This indicates the task is already complete.
694
- return;
996
+ function abortRemainingPendingTasks(task, error, debugInfo) {
997
+ let exitStatus;
998
+ if (task.status === 0) {
999
+ // The data for this segment is still missing.
1000
+ task.status = 2;
1001
+ abortPendingCacheNode(task.node, error, debugInfo);
1002
+ // If the server failed to fulfill the data for this segment, it implies
1003
+ // that the route tree received from the server mismatched the tree that
1004
+ // was previously prefetched.
1005
+ //
1006
+ // In an app with fully static routes and no proxy-driven redirects or
1007
+ // rewrites, this should never happen, because the route for a URL would
1008
+ // always be the same across multiple requests. So, this implies that some
1009
+ // runtime routing condition changed, likely in a proxy, without being
1010
+ // pushed to the client.
1011
+ //
1012
+ // When this happens, we treat this the same as a refresh(). The entire
1013
+ // tree will be re-rendered from the root.
1014
+ if (task.refreshUrl === null) {
1015
+ // Trigger a "soft" refresh. Essentially the same as calling `refresh()`
1016
+ // in a Server Action.
1017
+ exitStatus = 1;
1018
+ } else {
1019
+ // The mismatch was discovered inside an inactive parallel route. This
1020
+ // implies the inactive parallel route is no longer reachable at the URL
1021
+ // that originally rendered it. Fall back to an MPA refresh.
1022
+ // TODO: An alternative could be to trigger a soft refresh but to _not_
1023
+ // re-use the inactive parallel routes this time. Similar to what would
1024
+ // happen if were to do a hard refrehs, but without the HTML page.
1025
+ exitStatus = 2;
1026
+ }
1027
+ } else {
1028
+ // This segment finished. (An error here is treated as Done because they are
1029
+ // surfaced to the application during render.)
1030
+ exitStatus = 0;
695
1031
  }
696
1032
  const taskChildren = task.children;
697
- if (taskChildren === null) {
698
- // Reached the leaf task node. This is the root of a pending cache
699
- // node tree.
700
- abortPendingCacheNode(task.route, cacheNode, error, debugInfo);
701
- } else {
702
- // This is an intermediate task node. Keep traversing until we reach a
703
- // task node with no children. That will be the root of the cache node tree
704
- // that needs to be resolved.
705
- for (const taskChild of taskChildren.values()){
706
- abortTask(taskChild, error, debugInfo);
1033
+ if (taskChildren !== null) {
1034
+ for (const [, taskChild] of taskChildren){
1035
+ const childExitStatus = abortRemainingPendingTasks(taskChild, error, debugInfo);
1036
+ // Propagate the exit status up the tree. The statuses are ordered by
1037
+ // their precedence.
1038
+ if (childExitStatus > exitStatus) {
1039
+ exitStatus = childExitStatus;
1040
+ }
707
1041
  }
708
1042
  }
709
- // Set this to null to indicate that this task is now complete.
710
- task.dynamicRequestTree = null;
1043
+ return exitStatus;
711
1044
  }
712
- function abortPendingCacheNode(routerState, cacheNode, error, debugInfo) {
713
- // For every pending segment in the tree, resolve its `rsc` promise to `null`
714
- // to trigger a lazy fetch during render.
715
- //
716
- // Or, if an error object is provided, it will error instead.
717
- const routerStateChildren = routerState[1];
718
- const parallelRoutes = cacheNode.parallelRoutes;
719
- for(let parallelRouteKey in routerStateChildren){
720
- const routerStateChild = routerStateChildren[parallelRouteKey];
721
- const segmentMapChild = parallelRoutes.get(parallelRouteKey);
722
- if (segmentMapChild === undefined) {
723
- continue;
724
- }
725
- const segmentChild = routerStateChild[0];
726
- const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
727
- const cacheNodeChild = segmentMapChild.get(segmentKeyChild);
728
- if (cacheNodeChild !== undefined) {
729
- abortPendingCacheNode(routerStateChild, cacheNodeChild, error, debugInfo);
730
- } else {
731
- // This shouldn't happen because we're traversing the same tree that was
732
- // used to construct the cache nodes in the first place.
733
- }
734
- }
1045
+ function abortPendingCacheNode(cacheNode, error, debugInfo) {
735
1046
  const rsc = cacheNode.rsc;
736
1047
  if (isDeferredRsc(rsc)) {
737
1048
  if (error === null) {
@@ -755,64 +1066,7 @@ function abortPendingCacheNode(routerState, cacheNode, error, debugInfo) {
755
1066
  head.resolve(null, debugInfo);
756
1067
  }
757
1068
  }
758
- function updateCacheNodeOnPopstateRestoration(oldCacheNode, routerState) {
759
- // A popstate navigation reads data from the local cache. It does not issue
760
- // new network requests (unless the cache entries have been evicted). So, we
761
- // update the cache to drop the prefetch data for any segment whose dynamic
762
- // data was already received. This prevents an unnecessary flash back to PPR
763
- // state during a back/forward navigation.
764
- //
765
- // This function clones the entire cache node tree and sets the `prefetchRsc`
766
- // field to `null` to prevent it from being rendered. We can't mutate the node
767
- // in place because this is a concurrent data structure.
768
- const routerStateChildren = routerState[1];
769
- const oldParallelRoutes = oldCacheNode.parallelRoutes;
770
- const newParallelRoutes = new Map(oldParallelRoutes);
771
- for(let parallelRouteKey in routerStateChildren){
772
- const routerStateChild = routerStateChildren[parallelRouteKey];
773
- const segmentChild = routerStateChild[0];
774
- const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
775
- const oldSegmentMapChild = oldParallelRoutes.get(parallelRouteKey);
776
- if (oldSegmentMapChild !== undefined) {
777
- const oldCacheNodeChild = oldSegmentMapChild.get(segmentKeyChild);
778
- if (oldCacheNodeChild !== undefined) {
779
- const newCacheNodeChild = updateCacheNodeOnPopstateRestoration(oldCacheNodeChild, routerStateChild);
780
- const newSegmentMapChild = new Map(oldSegmentMapChild);
781
- newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
782
- newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
783
- }
784
- }
785
- }
786
- // Only show prefetched data if the dynamic data is still pending.
787
- //
788
- // Tehnically, what we're actually checking is whether the dynamic network
789
- // response was received. But since it's a streaming response, this does not
790
- // mean that all the dynamic data has fully streamed in. It just means that
791
- // _some_ of the dynamic data was received. But as a heuristic, we assume that
792
- // the rest dynamic data will stream in quickly, so it's still better to skip
793
- // the prefetch state.
794
- const rsc = oldCacheNode.rsc;
795
- const shouldUsePrefetch = isDeferredRsc(rsc) && rsc.status === 'pending';
796
- return {
797
- lazyData: null,
798
- rsc,
799
- head: oldCacheNode.head,
800
- prefetchHead: shouldUsePrefetch ? oldCacheNode.prefetchHead : [
801
- null,
802
- null
803
- ],
804
- prefetchRsc: shouldUsePrefetch ? oldCacheNode.prefetchRsc : null,
805
- loading: oldCacheNode.loading,
806
- // These are the cloned children we computed above
807
- parallelRoutes: newParallelRoutes,
808
- navigatedAt: oldCacheNode.navigatedAt
809
- };
810
- }
811
1069
  const DEFERRED = Symbol();
812
- // This type exists to distinguish a DeferredRsc from a Flight promise. It's a
813
- // compromise to avoid adding an extra field on every Cache Node, which would be
814
- // awkward because the pre-PPR parts of codebase would need to account for it,
815
- // too. We can remove it once type Cache Node type is more settled.
816
1070
  function isDeferredRsc(value) {
817
1071
  return value && typeof value === 'object' && value.tag === DEFERRED;
818
1072
  }