@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -93,15 +93,17 @@
93
93
  */
94
94
  import type { ResolvedSegment } from "../../types.js";
95
95
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
96
- import { getRouterContext } from "../router-context.js";
97
- import { resolveSink, safeEmit } from "../telemetry.js";
96
+ import { getRouterContext, type RouterContext } from "../router-context.js";
97
+ import { observeEvent } from "../instrument.js";
98
98
  import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
99
+ import { treeHasStreaming } from "./segment-resolution.js";
99
100
  import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
100
101
  import type { HandleStore } from "../../server/handle-store.js";
101
102
  import {
102
103
  getRequestContext,
103
104
  _getRequestContext,
104
105
  } from "../../server/request-context.js";
106
+ import { paramsEqual } from "../params-util.js";
105
107
 
106
108
  // Lazily initialized prerender store singleton and dynamically imported deps.
107
109
  // Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
@@ -113,6 +115,9 @@ let _deserializeSegments:
113
115
  let _restoreHandles:
114
116
  | typeof import("../../cache/handle-snapshot.js").restoreHandles
115
117
  | undefined;
118
+ let _decodeHandles:
119
+ | typeof import("../../cache/handle-snapshot.js").decodeHandles
120
+ | undefined;
116
121
  let _hashParams:
117
122
  | typeof import("../../prerender/param-hash.js").hashParams
118
123
  | undefined;
@@ -120,22 +125,6 @@ let _lazyGetRequestContext:
120
125
  | typeof import("../../server/request-context.js").getRequestContext
121
126
  | undefined;
122
127
 
123
- function paramsEqual(
124
- a: Record<string, string>,
125
- b: Record<string, string>,
126
- ): boolean {
127
- if (a === b) return true;
128
-
129
- const keysA = Object.keys(a);
130
- if (keysA.length !== Object.keys(b).length) return false;
131
-
132
- for (const key of keysA) {
133
- if (a[key] !== b[key]) return false;
134
- }
135
-
136
- return true;
137
- }
138
-
139
128
  async function ensurePrerenderDeps() {
140
129
  if (!_deserializeSegments) {
141
130
  const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
@@ -147,6 +136,7 @@ async function ensurePrerenderDeps() {
147
136
  ]);
148
137
  _deserializeSegments = codec.deserializeSegments;
149
138
  _restoreHandles = snapshot.restoreHandles;
139
+ _decodeHandles = snapshot.decodeHandles;
150
140
  _hashParams = paramHash.hashParams;
151
141
  _lazyGetRequestContext = reqCtx.getRequestContext;
152
142
  if (prerenderStoreInstance === undefined) {
@@ -160,59 +150,21 @@ async function ensurePrerenderDeps() {
160
150
  * Deserializes segments, replays handle data, yields segments with partial
161
151
  * navigation nullification, and resolves fresh loaders.
162
152
  */
163
- async function* yieldFromStore<TEnv>(
164
- entry: PrerenderEntry,
153
+ // Resolve loaders fresh on a cache hit (loaders are NEVER cached) and yield
154
+ // the loader segments, updating state.matchedIds and pushing the loader-resolve
155
+ // + cache-hit metrics. Shared verbatim by the prerender-store path
156
+ // (yieldFromStore) and the runtime cache-hit path (withCacheLookup). The router
157
+ // resolve fns are passed in (captured early by the callers) rather than re-read
158
+ // from getRouterContext() here, because that is ALS-backed and the callers run
159
+ // awaits before reaching this point (workerd can disrupt ALS mid-pipeline).
160
+ async function* resolveFreshLoadersAndYield<TEnv>(
165
161
  ctx: MatchContext<TEnv>,
166
162
  state: MatchPipelineState,
167
163
  pipelineStart: number,
168
- handleStoreRef?: HandleStore,
164
+ ms: MatchContext<TEnv>["metricsStore"],
165
+ resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
166
+ resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
169
167
  ): AsyncGenerator<ResolvedSegment> {
170
- const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
171
- getRouterContext<TEnv>();
172
-
173
- if (
174
- !_deserializeSegments ||
175
- !_restoreHandles ||
176
- !_hashParams ||
177
- !_lazyGetRequestContext
178
- ) {
179
- throw new Error("yieldFromStore called before ensurePrerenderDeps");
180
- }
181
-
182
- const segments = await _deserializeSegments(entry.segments);
183
-
184
- // Replay handle data (same as runtime cache hit path).
185
- // Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
186
- const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
187
- if (handleStore) {
188
- _restoreHandles(entry.handles, handleStore);
189
- }
190
-
191
- state.cacheHit = true;
192
- state.cacheSource = "prerender";
193
- state.cachedSegments = segments;
194
- state.cachedMatchedIds = segments.map((s) => s.id);
195
-
196
- // For partial navigation, nullify components the client already has
197
- // so parent layouts stay live (client keeps its existing versions).
198
- // When params changed (e.g., different guide slug), the segments have
199
- // different content, so we must NOT nullify.
200
- const paramsChanged =
201
- !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
202
- for (const segment of segments) {
203
- if (
204
- !ctx.isFullMatch &&
205
- !paramsChanged &&
206
- ctx.clientSegmentSet.has(segment.id)
207
- ) {
208
- segment.component = null;
209
- segment.loading = undefined;
210
- }
211
- yield segment;
212
- }
213
-
214
- // Resolve loaders fresh (loaders are never pre-rendered/cached)
215
- const ms = ctx.metricsStore;
216
168
  const loaderStart = performance.now();
217
169
 
218
170
  if (ctx.isFullMatch) {
@@ -240,6 +192,9 @@ async function* yieldFromStore<TEnv>(
240
192
  ctx.url,
241
193
  ctx.routeKey,
242
194
  ctx.actionContext,
195
+ // Loaders are never cached in the segment cache, so segment staleness
196
+ // must not propagate; browser-sent staleness (ctx.stale) still must.
197
+ ctx.stale || undefined,
243
198
  ),
244
199
  );
245
200
  state.matchedIds = [
@@ -270,6 +225,115 @@ async function* yieldFromStore<TEnv>(
270
225
  }
271
226
  }
272
227
 
228
+ async function* yieldFromStore<TEnv>(
229
+ entry: PrerenderEntry,
230
+ ctx: MatchContext<TEnv>,
231
+ state: MatchPipelineState,
232
+ pipelineStart: number,
233
+ handleStoreRef?: HandleStore,
234
+ ): AsyncGenerator<ResolvedSegment> {
235
+ const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
236
+ getRouterContext<TEnv>();
237
+
238
+ if (
239
+ !_deserializeSegments ||
240
+ !_restoreHandles ||
241
+ !_decodeHandles ||
242
+ !_hashParams ||
243
+ !_lazyGetRequestContext
244
+ ) {
245
+ throw new Error("yieldFromStore called before ensurePrerenderDeps");
246
+ }
247
+
248
+ const segments = await _deserializeSegments(entry.segments);
249
+
250
+ // Replay handle data (same as runtime cache hit path). entry.handles is a
251
+ // Flight-encoded string ("" when none) — decode before restore so
252
+ // Promise/ReactNode handle values are revived, not the corrupted JSON form.
253
+ const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
254
+ if (handleStore && entry.handles) {
255
+ const handlesRecord = await _decodeHandles(entry.handles);
256
+ if (handlesRecord) {
257
+ _restoreHandles(handlesRecord, handleStore);
258
+ }
259
+ }
260
+
261
+ state.cacheHit = true;
262
+ state.cacheSource = "prerender";
263
+ state.cachedSegments = segments;
264
+ state.cachedMatchedIds = segments.map((s) => s.id);
265
+
266
+ // Set streaming flag (once) and resolve render barrier.
267
+ const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
268
+ const barrierReqCtx = reqCtx ?? _getRequestContext();
269
+ if (barrierReqCtx) {
270
+ if (barrierReqCtx._treeHasStreaming === undefined) {
271
+ barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
272
+ }
273
+ barrierReqCtx._resolveRenderBarrier(segments);
274
+ }
275
+
276
+ // For partial navigation, nullify components the client already has
277
+ // so parent layouts stay live (client keeps its existing versions).
278
+ // When params changed (e.g., different guide slug), the segments have
279
+ // different content, so we must NOT nullify.
280
+ const paramsChanged =
281
+ !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
282
+ for (const segment of segments) {
283
+ if (
284
+ !ctx.isFullMatch &&
285
+ !paramsChanged &&
286
+ ctx.clientSegmentSet.has(segment.id)
287
+ ) {
288
+ segment.component = null;
289
+ segment.loading = undefined;
290
+ }
291
+ yield segment;
292
+ }
293
+
294
+ // Resolve loaders fresh (loaders are never pre-rendered/cached).
295
+ yield* resolveFreshLoadersAndYield(
296
+ ctx,
297
+ state,
298
+ pipelineStart,
299
+ ctx.metricsStore,
300
+ resolveLoadersOnly,
301
+ resolveLoadersOnlyWithRevalidation,
302
+ );
303
+ }
304
+
305
+ /**
306
+ * Look up a prerendered (build-time cached) entry for the current route and, on
307
+ * a hit, yield its segments. Returns true when an entry was served (the caller
308
+ * should stop the pipeline) and false on a miss. Intercept navigations consult
309
+ * only the intercept-specific entry (`paramHash + "/i"`); a miss there falls
310
+ * through to the normal pipeline so intercept-resolution can run. Callers must
311
+ * guard on `prerenderStoreInstance` after `ensurePrerenderDeps()`.
312
+ */
313
+ async function* tryPrerenderLookup<TEnv>(
314
+ ctx: MatchContext<TEnv>,
315
+ state: MatchPipelineState,
316
+ pipelineStart: number,
317
+ handleStoreRef?: HandleStore,
318
+ ): AsyncGenerator<ResolvedSegment, boolean> {
319
+ const paramHash = _hashParams!(ctx.matched.params);
320
+ const isPassthroughPrerenderRoute = ctx.entries.some(
321
+ (entry) => entry.type === "route" && entry.isPassthrough === true,
322
+ );
323
+ const lookupHash = ctx.isIntercept ? paramHash + "/i" : paramHash;
324
+ const entry = await prerenderStoreInstance!.get(
325
+ ctx.matched.routeKey,
326
+ lookupHash,
327
+ {
328
+ pathname: ctx.pathname,
329
+ isPassthroughRoute: isPassthroughPrerenderRoute,
330
+ },
331
+ );
332
+ if (!entry) return false;
333
+ yield* yieldFromStore(entry, ctx, state, pipelineStart, handleStoreRef);
334
+ return true;
335
+ }
336
+
273
337
  /**
274
338
  * Async generator middleware type
275
339
  */
@@ -314,72 +378,20 @@ export function withCacheLookup<TEnv>(
314
378
  resolveLoadersOnly,
315
379
  } = getRouterContext<TEnv>();
316
380
 
317
- // Prerender lookup: check build-time cached data before runtime cache.
318
- // Prerender data is available regardless of runtime cache configuration.
319
- if (!ctx.isAction && ctx.matched.pr) {
381
+ const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
382
+ if (!ctx.isAction && !isHmr && ctx.matched.pr) {
320
383
  await ensurePrerenderDeps();
321
384
  if (prerenderStoreInstance) {
322
- const paramHash = _hashParams!(ctx.matched.params);
323
- const isPassthroughPrerenderRoute = ctx.entries.some(
324
- (entry) =>
325
- entry.type === "route" &&
326
- entry.prerenderDef?.options?.passthrough === true,
385
+ const served = yield* tryPrerenderLookup(
386
+ ctx,
387
+ state,
388
+ pipelineStart,
389
+ handleStoreRef,
327
390
  );
328
-
329
- if (ctx.isIntercept) {
330
- // Intercept navigation: try intercept-specific prerender entry
331
- const entry = await prerenderStoreInstance.get(
332
- ctx.matched.routeKey,
333
- paramHash + "/i",
334
- {
335
- pathname: ctx.pathname,
336
- isPassthroughRoute: isPassthroughPrerenderRoute,
337
- },
338
- );
339
- if (entry) {
340
- yield* yieldFromStore(
341
- entry,
342
- ctx,
343
- state,
344
- pipelineStart,
345
- handleStoreRef,
346
- );
347
- return;
348
- }
349
- // No intercept prerender -- fall through to normal pipeline
350
- // (skip non-intercept prerender to let intercept-resolution run)
351
- } else {
352
- // Normal navigation: existing behavior
353
- const entry = await prerenderStoreInstance.get(
354
- ctx.matched.routeKey,
355
- paramHash,
356
- {
357
- pathname: ctx.pathname,
358
- isPassthroughRoute: isPassthroughPrerenderRoute,
359
- },
360
- );
361
- if (entry) {
362
- yield* yieldFromStore(
363
- entry,
364
- ctx,
365
- state,
366
- pipelineStart,
367
- handleStoreRef,
368
- );
369
- return;
370
- }
371
- }
391
+ if (served) return;
372
392
  }
373
393
  }
374
394
 
375
- // Dev-mode static handler interception for non-Node.js runtimes.
376
- // __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
377
- // lacks a Node.js module runner (e.g. workerd, Deno workers). In those
378
- // runtimes, handlers that depend on Node APIs like node:fs can't run
379
- // in-process. We redirect them to the /__rsc_prerender endpoint which
380
- // resolves segments in a Node.js temp server, same as prerender routes.
381
- // In Node.js dev mode this variable is undefined -- handlers run
382
- // in-process where Node APIs work, so no interception is needed.
383
395
  if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
384
396
  const hasStatic = ctx.entries.some(
385
397
  (e) =>
@@ -391,60 +403,18 @@ export function withCacheLookup<TEnv>(
391
403
  if (hasStatic) {
392
404
  await ensurePrerenderDeps();
393
405
  if (prerenderStoreInstance) {
394
- const paramHash = _hashParams!(ctx.matched.params);
395
- const isPassthroughPrerenderRoute = ctx.entries.some(
396
- (entry) =>
397
- entry.type === "route" &&
398
- entry.prerenderDef?.options?.passthrough === true,
406
+ const served = yield* tryPrerenderLookup(
407
+ ctx,
408
+ state,
409
+ pipelineStart,
410
+ handleStoreRef,
399
411
  );
400
-
401
- if (ctx.isIntercept) {
402
- const entry = await prerenderStoreInstance.get(
403
- ctx.matched.routeKey,
404
- paramHash + "/i",
405
- {
406
- pathname: ctx.pathname,
407
- isPassthroughRoute: isPassthroughPrerenderRoute,
408
- },
409
- );
410
- if (entry) {
411
- yield* yieldFromStore(
412
- entry,
413
- ctx,
414
- state,
415
- pipelineStart,
416
- handleStoreRef,
417
- );
418
- return;
419
- }
420
- // No intercept prerender -- fall through to normal pipeline
421
- } else {
422
- const entry = await prerenderStoreInstance.get(
423
- ctx.matched.routeKey,
424
- paramHash,
425
- {
426
- pathname: ctx.pathname,
427
- isPassthroughRoute: isPassthroughPrerenderRoute,
428
- },
429
- );
430
- if (entry) {
431
- yield* yieldFromStore(
432
- entry,
433
- ctx,
434
- state,
435
- pipelineStart,
436
- handleStoreRef,
437
- );
438
- return;
439
- }
440
- }
412
+ if (served) return;
441
413
  }
442
414
  }
443
415
  }
444
416
 
445
- // Skip cache during actions
446
417
  if (ctx.isAction || !ctx.cacheScope?.enabled) {
447
- // Cache miss - pass through to segment resolution
448
418
  yield* source;
449
419
  if (ms) {
450
420
  ms.metrics.push({
@@ -456,7 +426,6 @@ export function withCacheLookup<TEnv>(
456
426
  return;
457
427
  }
458
428
 
459
- // Lookup cache
460
429
  const cacheResult = await ctx.cacheScope.lookupRoute(
461
430
  ctx.pathname,
462
431
  ctx.matched.params,
@@ -464,7 +433,6 @@ export function withCacheLookup<TEnv>(
464
433
  );
465
434
 
466
435
  if (!cacheResult) {
467
- // Cache miss - pass through to segment resolution
468
436
  yield* source;
469
437
  if (ms) {
470
438
  ms.metrics.push({
@@ -476,16 +444,12 @@ export function withCacheLookup<TEnv>(
476
444
  return;
477
445
  }
478
446
 
479
- // Cache HIT
480
447
  state.cacheHit = true;
481
448
  state.cacheSource = "runtime";
482
449
  state.shouldRevalidate = cacheResult.shouldRevalidate;
483
450
  state.cachedSegments = cacheResult.segments;
484
451
  state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
485
452
 
486
- // Apply revalidation to cached segments.
487
- // For full matches or empty client segment sets, this map is unnecessary:
488
- // we never run segment-level revalidation and can stream segments directly.
489
453
  const canCheckSegmentRevalidation =
490
454
  !ctx.isFullMatch &&
491
455
  ctx.clientSegmentSet.size > 0 &&
@@ -495,7 +459,6 @@ export function withCacheLookup<TEnv>(
495
459
  : undefined;
496
460
 
497
461
  for (const segment of cacheResult.segments) {
498
- // Skip segments client doesn't have - they need their component
499
462
  if (!ctx.clientSegmentSet.has(segment.id)) {
500
463
  if (isTraceActive()) {
501
464
  pushRevalidationTraceEntry({
@@ -512,19 +475,13 @@ export function withCacheLookup<TEnv>(
512
475
  continue;
513
476
  }
514
477
 
515
- // Skip intercept segments - they're handled separately
516
478
  if (segment.namespace?.startsWith("intercept:")) {
517
479
  yield segment;
518
480
  continue;
519
481
  }
520
482
 
521
- // Look up revalidation rules for this segment
522
483
  const entryInfo = entryRevalidateMap?.get(segment.id);
523
484
 
524
- // Even without explicit revalidation rules, route segments and their
525
- // children must re-render when params or search params change — the
526
- // handler reads ctx.params/ctx.searchParams so different values produce
527
- // different content. Matches evaluateRevalidation's default logic.
528
485
  const searchChanged = ctx.prevUrl.search !== ctx.url.search;
529
486
  const routeParamsChanged = !paramsEqual(
530
487
  ctx.matched.params,
@@ -538,7 +495,6 @@ export function withCacheLookup<TEnv>(
538
495
 
539
496
  if (!entryInfo || entryInfo.revalidate.length === 0) {
540
497
  if (shouldDefaultRevalidate) {
541
- // Params or search params changed — must re-render even without custom rules
542
498
  if (isTraceActive()) {
543
499
  pushRevalidationTraceEntry({
544
500
  segmentId: segment.id,
@@ -555,7 +511,6 @@ export function withCacheLookup<TEnv>(
555
511
  yield segment;
556
512
  continue;
557
513
  }
558
- // No revalidation rules, use default behavior (skip if client has)
559
514
  if (isTraceActive()) {
560
515
  pushRevalidationTraceEntry({
561
516
  segmentId: segment.id,
@@ -573,7 +528,6 @@ export function withCacheLookup<TEnv>(
573
528
  continue;
574
529
  }
575
530
 
576
- // Evaluate revalidation rules
577
531
  const shouldRevalidate = await evaluateRevalidation({
578
532
  segment,
579
533
  prevParams: ctx.prevParams,
@@ -588,26 +542,20 @@ export function withCacheLookup<TEnv>(
588
542
  routeKey: ctx.routeKey,
589
543
  context: ctx.handlerContext,
590
544
  actionContext: ctx.actionContext,
591
- stale: cacheResult.shouldRevalidate || undefined,
545
+ stale: cacheResult.shouldRevalidate || ctx.stale || undefined,
592
546
  traceSource: "cache-hit",
593
547
  });
594
548
 
595
- const routerCtx = getRouterContext<TEnv>();
596
- if (routerCtx.telemetry) {
597
- const tSink = resolveSink(routerCtx.telemetry);
598
- safeEmit(tSink, {
599
- type: "revalidation.decision",
600
- timestamp: performance.now(),
601
- requestId: routerCtx.requestId,
602
- segmentId: segment.id,
603
- pathname: ctx.pathname,
604
- routeKey: ctx.routeKey,
605
- shouldRevalidate,
606
- });
607
- }
549
+ observeEvent({
550
+ type: "revalidation.decision",
551
+ timestamp: performance.now(),
552
+ segmentId: segment.id,
553
+ pathname: ctx.pathname,
554
+ routeKey: ctx.routeKey,
555
+ shouldRevalidate,
556
+ });
608
557
 
609
558
  if (!shouldRevalidate) {
610
- // Client has it, no revalidation needed
611
559
  segment.component = null;
612
560
  segment.loading = undefined;
613
561
  }
@@ -615,77 +563,23 @@ export function withCacheLookup<TEnv>(
615
563
  yield segment;
616
564
  }
617
565
 
618
- // Resolve loaders fresh (loaders are NOT cached by default)
619
- // This ensures fresh data even on cache hit
620
- const Store = ctx.Store;
621
- const loaderStart = performance.now();
622
-
623
- if (ctx.isFullMatch) {
624
- // Full match (document request) - simple loader resolution without revalidation
625
- if (resolveLoadersOnly) {
626
- const loaderSegments = await Store.run(() =>
627
- resolveLoadersOnly(ctx.entries, ctx.handlerContext),
628
- );
629
-
630
- // Update state - full match doesn't track matchedIds separately
631
- state.matchedIds = state.cachedMatchedIds!;
632
-
633
- // Yield fresh loader segments
634
- for (const segment of loaderSegments) {
635
- yield segment;
636
- }
637
- } else {
638
- state.matchedIds = state.cachedMatchedIds!;
639
- }
640
- } else {
641
- // Partial match (navigation) - loader resolution with revalidation
642
- if (resolveLoadersOnlyWithRevalidation) {
643
- const loaderResult = await Store.run(() =>
644
- resolveLoadersOnlyWithRevalidation(
645
- ctx.entries,
646
- ctx.handlerContext,
647
- ctx.clientSegmentSet,
648
- ctx.prevParams,
649
- ctx.request,
650
- ctx.prevUrl,
651
- ctx.url,
652
- ctx.routeKey,
653
- ctx.actionContext,
654
- // Loaders are never cached in the segment cache, so segment
655
- // staleness (cacheResult.shouldRevalidate) must not propagate.
656
- // But browser-sent staleness (ctx.stale) — indicating an action
657
- // happened in this or another tab — must still reach loaders.
658
- ctx.stale || undefined,
659
- ),
660
- );
661
-
662
- // Update state with fresh loader matchedIds
663
- state.matchedIds = [
664
- ...state.cachedMatchedIds!,
665
- ...loaderResult.matchedIds,
666
- ];
667
-
668
- // Yield fresh loader segments
669
- for (const segment of loaderResult.segments) {
670
- yield segment;
671
- }
672
- } else {
673
- state.matchedIds = state.cachedMatchedIds!;
566
+ const barrierReqCtx = _getRequestContext();
567
+ if (barrierReqCtx) {
568
+ if (barrierReqCtx._treeHasStreaming === undefined) {
569
+ barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
674
570
  }
571
+ barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
675
572
  }
676
- if (ms) {
677
- const loaderEnd = performance.now();
678
- ms.metrics.push({
679
- label: "pipeline:loader-resolve",
680
- duration: loaderEnd - loaderStart,
681
- startTime: loaderStart - ms.requestStart,
682
- depth: 1,
683
- });
684
- ms.metrics.push({
685
- label: "pipeline:cache-hit",
686
- duration: loaderEnd - pipelineStart,
687
- startTime: pipelineStart - ms.requestStart,
688
- });
689
- }
573
+
574
+ // Resolve loaders fresh (loaders are never cached). Shared with the
575
+ // prerender-store path via resolveFreshLoadersAndYield.
576
+ yield* resolveFreshLoadersAndYield(
577
+ ctx,
578
+ state,
579
+ pipelineStart,
580
+ ms,
581
+ resolveLoadersOnly,
582
+ resolveLoadersOnlyWithRevalidation,
583
+ );
690
584
  };
691
585
  }