@wular/pnext 0.0.4 → 0.0.7

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 (395) hide show
  1. package/README.md +22 -16
  2. package/bin/pnext +6 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +87 -22
  8. package/reference/config.md +40 -93
  9. package/reference/css.md +42 -33
  10. package/reference/dev.md +26 -33
  11. package/reference/env.md +18 -20
  12. package/reference/getting-started.md +132 -0
  13. package/reference/metadata.md +31 -46
  14. package/reference/navigation.md +65 -105
  15. package/reference/performance.md +21 -142
  16. package/reference/rendering.md +78 -75
  17. package/reference/routing.md +97 -107
  18. package/reference/typegen.md +27 -29
  19. package/src/api/cache.ts +36 -37
  20. package/src/api/client-cache.ts +2 -2
  21. package/src/api/client-navigation.ts +121 -114
  22. package/src/api/dynamic.tsx +58 -55
  23. package/src/api/link.tsx +52 -53
  24. package/src/api/navigation.ts +59 -58
  25. package/src/api/server.ts +152 -144
  26. package/src/api/suspense.ts +4 -4
  27. package/src/cli/adapters/vercel-warm.ts +126 -121
  28. package/src/cli/adapters/vercel.ts +437 -443
  29. package/src/cli/analyze.ts +333 -144
  30. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  31. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  32. package/src/cli/build.ts +976 -978
  33. package/src/cli/create.ts +72 -64
  34. package/src/cli/dev.ts +116 -116
  35. package/src/cli/index.ts +94 -85
  36. package/src/cli/migrate/package-json.ts +73 -71
  37. package/src/cli/migrate/report.ts +32 -33
  38. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  39. package/src/cli/migrate/scan.ts +46 -46
  40. package/src/cli/migrate/spinner.ts +9 -9
  41. package/src/cli/migrate/tsconfig.ts +35 -35
  42. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  43. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  44. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  45. package/src/cli/start.ts +65 -66
  46. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  47. package/src/client/build.ts +802 -768
  48. package/src/client/chunk-fold.ts +245 -240
  49. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  50. package/src/client/entry.ts +162 -147
  51. package/src/client/prebuilt.ts +231 -223
  52. package/src/client/profile.ts +29 -29
  53. package/src/client/react-compiler.ts +20 -15
  54. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  55. package/src/client/reference-stub.ts +51 -51
  56. package/src/client/reference.ts +19 -19
  57. package/src/{api → client}/router/events.ts +32 -17
  58. package/src/{api → client}/router/history.ts +16 -16
  59. package/src/{api → client}/router/hub.ts +52 -53
  60. package/src/{api/router.ts → client/router/index.ts} +61 -60
  61. package/src/{api → client}/router/policies.ts +24 -24
  62. package/src/{api → client}/router/runtime.ts +2022 -1995
  63. package/src/{api → client}/router/types.ts +98 -98
  64. package/src/compat/actions/client-plugin.ts +42 -43
  65. package/src/compat/actions/client-stub.ts +14 -14
  66. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  67. package/src/compat/actions/config.ts +63 -64
  68. package/src/compat/actions/detect.ts +68 -68
  69. package/src/compat/actions/discovery.ts +105 -105
  70. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  71. package/src/compat/actions/early-submit.ts +1 -1
  72. package/src/compat/actions/endpoint.ts +198 -198
  73. package/src/compat/actions/extensions.ts +811 -0
  74. package/src/compat/actions/flight.ts +23 -23
  75. package/src/compat/actions/form-state.ts +34 -34
  76. package/src/compat/actions/hoist.ts +382 -236
  77. package/src/compat/actions/ids.ts +7 -12
  78. package/src/compat/actions/index.ts +8 -8
  79. package/src/compat/actions/instances.ts +46 -46
  80. package/src/compat/actions/origin.ts +47 -48
  81. package/src/compat/actions/protocol.ts +22 -22
  82. package/src/compat/actions/registry.ts +19 -19
  83. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  84. package/src/compat/actions/rewrite.ts +125 -125
  85. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  86. package/src/compat/actions/serve.ts +132 -136
  87. package/src/compat/actions/server-tag.ts +4 -4
  88. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  89. package/src/compat/actions/unrecognized-error.ts +4 -4
  90. package/src/compat/{index.ts → aliases.ts} +112 -109
  91. package/src/compat/bundler/bun-externals.ts +18 -18
  92. package/src/compat/bundler/cjs-exports.ts +271 -223
  93. package/src/compat/bundler/config.ts +116 -111
  94. package/src/compat/bundler/externals.ts +12 -12
  95. package/src/compat/bundler/import-meta-url.ts +19 -19
  96. package/src/compat/bundler/modularize-imports.ts +36 -33
  97. package/src/compat/bundler/new-url-asset.ts +22 -24
  98. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  99. package/src/compat/bundler/polyfill.ts +28 -28
  100. package/src/compat/bundler/react-compiler.ts +35 -29
  101. package/src/compat/bundler/react-profiler.tsx +11 -11
  102. package/src/compat/bundler/relay-transform.ts +48 -47
  103. package/src/compat/bundler/require-context.ts +119 -113
  104. package/src/compat/bundler/resolve-extensions.ts +19 -19
  105. package/src/compat/bundler/source-cache.ts +25 -25
  106. package/src/compat/bundler/static-imports.ts +7 -7
  107. package/src/compat/bundler/symlink-imports.ts +46 -46
  108. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  109. package/src/compat/bundler/wasm.ts +58 -60
  110. package/src/compat/bundler/webpack-loaders.ts +254 -241
  111. package/src/compat/bundler/worker.ts +101 -104
  112. package/src/compat/cache/build-flags.ts +29 -29
  113. package/src/compat/cache/build-prerender-errors.ts +40 -44
  114. package/src/compat/cache/custom-handler.ts +60 -53
  115. package/src/compat/cache/fetch-patch.ts +240 -240
  116. package/src/compat/cache/handler.ts +27 -31
  117. package/src/compat/cache/modern-handler.ts +148 -126
  118. package/src/compat/cache/resume-data-cache.ts +47 -45
  119. package/src/compat/cache/revalidate.ts +233 -232
  120. package/src/compat/cache/runtime-error.ts +35 -35
  121. package/src/compat/cache/use-cache-transform.ts +442 -417
  122. package/src/compat/cache/use-cache.ts +650 -614
  123. package/src/compat/cache-control.ts +140 -142
  124. package/src/compat/client/base-path.ts +21 -20
  125. package/src/compat/client/css-order.ts +18 -18
  126. package/src/compat/client/errors/bare-boundary.ts +11 -11
  127. package/src/compat/client/errors/control-flow.ts +23 -23
  128. package/src/compat/client/errors/error-boundary.ts +61 -61
  129. package/src/compat/client/errors/global-error.ts +101 -91
  130. package/src/compat/client/errors/install.ts +71 -72
  131. package/src/compat/client/errors/lazy.ts +23 -23
  132. package/src/compat/client/errors/primitive-throw.ts +47 -45
  133. package/src/compat/client/errors/soft-refresh.ts +4 -4
  134. package/src/compat/client/link-status.ts +33 -33
  135. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  136. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  137. package/src/compat/client/navigation-scroll.ts +63 -63
  138. package/src/compat/client/optimistic-routing.ts +93 -88
  139. package/src/compat/client/prefetch-cache.ts +23 -24
  140. package/src/compat/client/route-announcer.ts +35 -35
  141. package/src/compat/client/segment-cache-policy.ts +20 -20
  142. package/src/compat/client/segment-cache.ts +309 -315
  143. package/src/compat/client/segment-prefetch.ts +127 -132
  144. package/src/compat/client/trailing-slash.ts +5 -4
  145. package/src/compat/css/chunking.ts +113 -116
  146. package/src/compat/css/inline-css.ts +21 -21
  147. package/src/compat/css/lightningcss.ts +37 -38
  148. package/src/compat/css/modules.ts +161 -175
  149. package/src/compat/css/nonce.ts +7 -7
  150. package/src/compat/css/sass-plugin.ts +18 -21
  151. package/src/compat/css/sass.ts +150 -152
  152. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  153. package/src/compat/css/styled-jsx.ts +21 -21
  154. package/src/compat/edge-runtime.ts +27 -27
  155. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  156. package/src/compat/export/client.ts +29 -31
  157. package/src/compat/export/{index.ts → emit.ts} +111 -110
  158. package/src/compat/export/standalone.ts +62 -54
  159. package/src/compat/image-optimizer/cache.ts +51 -49
  160. package/src/compat/image-optimizer/detect.ts +52 -52
  161. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  162. package/src/compat/image-optimizer/source.ts +77 -80
  163. package/src/compat/lifecycle/after-scope.ts +26 -26
  164. package/src/compat/lifecycle/after.ts +48 -45
  165. package/src/compat/lifecycle/error-funnel.ts +98 -102
  166. package/src/compat/lifecycle/error-serialize.ts +30 -26
  167. package/src/compat/lifecycle/error-ui.ts +67 -32
  168. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  169. package/src/compat/lifecycle/instrumentation.ts +85 -85
  170. package/src/compat/lifecycle/node-console.ts +11 -11
  171. package/src/compat/lifecycle/testmode.ts +160 -132
  172. package/src/compat/mdx/compile.ts +60 -57
  173. package/src/compat/mdx/plugin.ts +11 -11
  174. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  175. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  176. package/src/compat/metadata.ts +75 -81
  177. package/src/compat/next/cache.ts +68 -69
  178. package/src/compat/next/canonical-url.ts +9 -9
  179. package/src/compat/next/client-cache.ts +19 -19
  180. package/src/compat/next/client-navigation.ts +112 -116
  181. package/src/compat/next/client-only.ts +1 -1
  182. package/src/compat/next/client-script.tsx +99 -93
  183. package/src/compat/next/client-server.ts +10 -10
  184. package/src/compat/next/config-loader.ts +176 -173
  185. package/src/compat/next/config.ts +7 -7
  186. package/src/compat/next/constants.cjs +6 -6
  187. package/src/compat/next/constants.ts +6 -6
  188. package/src/compat/next/custom-server.ts +26 -24
  189. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  190. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  191. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  192. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  193. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  194. package/src/compat/next/dynamic.tsx +21 -18
  195. package/src/compat/next/error.tsx +52 -52
  196. package/src/compat/next/font/cache.ts +74 -74
  197. package/src/compat/next/font/google.ts +2 -2
  198. package/src/compat/next/font/index.ts +1 -1
  199. package/src/compat/next/font/local.ts +3 -3
  200. package/src/compat/next/font/runtime-client.ts +17 -15
  201. package/src/compat/next/font/runtime.ts +443 -408
  202. package/src/compat/next/font/shared.ts +120 -108
  203. package/src/compat/next/form.tsx +63 -63
  204. package/src/compat/next/head.tsx +2 -2
  205. package/src/compat/next/headers.ts +103 -95
  206. package/src/compat/next/image/client.tsx +220 -0
  207. package/src/compat/next/image/config.ts +56 -58
  208. package/src/compat/next/image/optimizer.ts +40 -36
  209. package/src/compat/next/image/patterns.ts +37 -40
  210. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  211. package/src/compat/next/image/shared.ts +65 -57
  212. package/src/compat/next/image/static-metadata.ts +98 -107
  213. package/src/compat/next/image/validate.ts +79 -88
  214. package/src/compat/next/image.tsx +19 -23
  215. package/src/compat/next/index.ts +1 -1
  216. package/src/compat/next/legacy-image.tsx +59 -60
  217. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  218. package/src/compat/next/link.tsx +158 -158
  219. package/src/compat/next/navigation.cjs +12 -3
  220. package/src/compat/next/navigation.ts +48 -50
  221. package/src/compat/next/offline.ts +27 -27
  222. package/src/compat/next/og.ts +121 -124
  223. package/src/compat/next/preferred-region.ts +13 -14
  224. package/src/compat/next/redirects.ts +58 -56
  225. package/src/compat/next/resource-hints.ts +73 -76
  226. package/src/compat/next/rewrites.ts +130 -133
  227. package/src/compat/next/root-params.ts +45 -45
  228. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  229. package/src/compat/next/router.cjs +4 -2
  230. package/src/compat/next/router.ts +58 -61
  231. package/src/compat/next/script.tsx +108 -108
  232. package/src/compat/next/server-only.ts +1 -1
  233. package/src/compat/next/server.ts +19 -19
  234. package/src/compat/next/svgr.ts +18 -17
  235. package/src/compat/next/telemetry.ts +24 -24
  236. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  237. package/src/compat/next/user-agent.ts +53 -49
  238. package/src/compat/next/web-vitals.ts +22 -24
  239. package/src/compat/otel/api.ts +41 -41
  240. package/src/compat/otel/client-trace-metadata.ts +25 -27
  241. package/src/compat/otel/fetch-span.ts +29 -29
  242. package/src/compat/otel/tracer.ts +331 -331
  243. package/src/compat/pages/api.ts +456 -0
  244. package/src/compat/pages/client-plugin.ts +36 -36
  245. package/src/compat/pages/router-state.ts +34 -34
  246. package/src/compat/pages/{index.ts → router.ts} +130 -135
  247. package/src/compat/ppr/io.ts +12 -12
  248. package/src/compat/ppr/missing-root-params.ts +34 -38
  249. package/src/compat/ppr/root-params-scan.ts +66 -66
  250. package/src/compat/ppr/root-params-transform.ts +24 -26
  251. package/src/compat/ppr/root-params.ts +30 -30
  252. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  253. package/src/compat/protocol.ts +71 -70
  254. package/src/compat/react/action-state.ts +47 -48
  255. package/src/compat/react/client-lite.ts +15 -15
  256. package/src/compat/react/client.ts +4 -4
  257. package/src/compat/react/compiler-runtime.ts +11 -11
  258. package/src/compat/react/dom-client.ts +44 -44
  259. package/src/compat/react/dom-react-server.ts +10 -16
  260. package/src/compat/react/dom-server.ts +10 -10
  261. package/src/compat/react/dom.ts +52 -52
  262. package/src/compat/react/hooks-extra.ts +34 -35
  263. package/src/compat/react/parity.ts +64 -61
  264. package/src/compat/react/preact.ts +81 -82
  265. package/src/compat/react/react-server.ts +28 -28
  266. package/src/compat/react/router-shim.ts +1 -1
  267. package/src/compat/react/server-component-use.ts +8 -8
  268. package/src/compat/react/server-inserted-html.ts +30 -31
  269. package/src/compat/react/server.ts +53 -55
  270. package/src/compat/react/use.ts +32 -32
  271. package/src/compat/react/view-transition.ts +20 -20
  272. package/src/compat/register/actions.ts +35 -824
  273. package/src/compat/register/boot.ts +41 -41
  274. package/src/compat/register/build-tier.ts +5 -5
  275. package/src/compat/register/build.ts +74 -70
  276. package/src/compat/register/bundler.ts +161 -156
  277. package/src/compat/register/cache.ts +20 -20
  278. package/src/compat/register/client-errors.ts +3 -3
  279. package/src/compat/register/config.ts +6 -6
  280. package/src/compat/register/css-extras.ts +30 -34
  281. package/src/compat/register/edge-runtime.ts +3 -3
  282. package/src/compat/register/errors.ts +10 -12
  283. package/src/compat/register/export.ts +16 -16
  284. package/src/compat/register/font.ts +11 -11
  285. package/src/compat/register/hooks.ts +2 -2
  286. package/src/compat/register/image.ts +40 -40
  287. package/src/compat/register/index.ts +59 -62
  288. package/src/compat/register/instrumentation-client.ts +10 -10
  289. package/src/compat/register/lifecycle.ts +25 -28
  290. package/src/compat/register/mdx.ts +10 -10
  291. package/src/compat/register/middleware.ts +226 -16
  292. package/src/compat/register/otel.ts +80 -88
  293. package/src/compat/register/pages-api.ts +10 -463
  294. package/src/compat/register/ppr.ts +16 -16
  295. package/src/compat/register/protocol.ts +17 -18
  296. package/src/compat/register/proxy.ts +49 -51
  297. package/src/compat/register/render.ts +79 -76
  298. package/src/compat/register/routing.ts +162 -159
  299. package/src/compat/register/segment.ts +24 -1897
  300. package/src/compat/register/static-image.ts +3 -3
  301. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  302. package/src/compat/register/typed-routes.ts +14 -14
  303. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  304. package/src/compat/register/validation.ts +18 -18
  305. package/src/compat/segment/loading-boundary.ts +43 -45
  306. package/src/compat/segment/page-slot.ts +69 -69
  307. package/src/compat/segment/serve.ts +1884 -0
  308. package/src/compat/segment/tree.ts +113 -112
  309. package/src/compat/segment/vary-key.ts +38 -38
  310. package/src/compat/segment/vary-params.ts +138 -142
  311. package/src/compat/static-params.ts +14 -12
  312. package/src/compat/tsconfig-defaults.ts +87 -91
  313. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  314. package/src/compat/typecheck/worker.ts +15 -12
  315. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  316. package/src/compat/typed-routes/manifest.ts +174 -170
  317. package/src/compat/typed-routes/typegen.ts +127 -110
  318. package/src/compat/validation/errors.ts +16 -19
  319. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  320. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  321. package/src/compat-bootstrap.ts +16 -16
  322. package/src/config.ts +86 -69
  323. package/src/css/build.ts +230 -227
  324. package/src/css/postcss.ts +79 -80
  325. package/src/css/worker.ts +14 -15
  326. package/src/dev/client-actions.ts +10 -10
  327. package/src/dev/client-chunk-store.ts +27 -27
  328. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  329. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  330. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  331. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  332. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  333. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  334. package/src/dev/server.ts +804 -820
  335. package/src/env.ts +46 -43
  336. package/src/extensions.ts +491 -478
  337. package/src/index.ts +8 -8
  338. package/src/internal.ts +20 -23
  339. package/src/{islands → render}/boundary-error.ts +3 -3
  340. package/src/render/hooks.ts +71 -71
  341. package/src/render/island-context.ts +14 -14
  342. package/src/render/metadata.ts +310 -310
  343. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  344. package/src/{ppr.ts → render/ppr.ts} +244 -245
  345. package/src/render/renderer.ts +2087 -2082
  346. package/src/render/resource-hints.ts +16 -17
  347. package/src/render/slots.tsx +224 -235
  348. package/src/{islands → render}/static-children.ts +9 -12
  349. package/src/{islands → render}/static-slots.ts +37 -37
  350. package/src/{cache/context.ts → request/cache.ts} +20 -20
  351. package/src/request/context.ts +107 -107
  352. package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
  353. package/src/resolve/engine.ts +90 -77
  354. package/src/resolve/imports.ts +475 -463
  355. package/src/resolve/scan-facts.ts +318 -296
  356. package/src/resolve/source-text.ts +37 -37
  357. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  358. package/src/routing/forwarded.ts +19 -19
  359. package/src/routing/handler.ts +84 -91
  360. package/src/routing/href.ts +87 -70
  361. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  362. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  363. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  364. package/src/routing/routes.ts +827 -815
  365. package/src/routing/slots.ts +164 -160
  366. package/src/runtime/loader.ts +954 -0
  367. package/src/{dev → runtime}/module-cache.ts +309 -287
  368. package/src/{dev → runtime}/module-generations.ts +9 -9
  369. package/src/{dev → runtime}/module-transform.ts +81 -72
  370. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  371. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  372. package/src/runtime/vendor.ts +428 -406
  373. package/src/styles.d.ts +9 -0
  374. package/src/types.ts +324 -335
  375. package/src/utils/ansi.ts +5 -5
  376. package/src/utils/{source.ts → code.ts} +15 -12
  377. package/src/utils/content-type.ts +3 -3
  378. package/src/utils/decode.ts +2 -2
  379. package/src/utils/dev-profile.ts +13 -13
  380. package/src/utils/error-log.ts +6 -6
  381. package/src/utils/esbuild.ts +18 -18
  382. package/src/utils/fs-cache.ts +13 -13
  383. package/src/utils/fs.ts +57 -49
  384. package/src/utils/html.ts +20 -24
  385. package/src/utils/native-require.ts +8 -8
  386. package/src/utils/serialize.ts +139 -146
  387. package/src/utils/verbose.ts +18 -18
  388. package/reference/overview.md +0 -35
  389. package/src/cli/analyze-print.ts +0 -181
  390. package/src/compat/middleware/manifest.ts +0 -210
  391. package/src/compat/next/image-client.tsx +0 -215
  392. package/src/compat/next/link-usage.ts +0 -29
  393. package/src/css/index.ts +0 -2
  394. package/src/render/index.ts +0 -1
  395. package/src/style-modules.d.ts +0 -9
@@ -25,16 +25,16 @@ import {
25
25
  segmentVaryCacheKey,
26
26
  SEARCH_PARAMS_SENTINEL,
27
27
  VARY_KEY_SEPARATOR,
28
- } from '../segment/vary-key';
28
+ } from '../segment/vary-key'
29
29
  import {
30
30
  composeSegmentFrames,
31
31
  pageSlotRange,
32
32
  stripPageSlotContent,
33
33
  stripStreamedContinuation,
34
- } from '../segment/page-slot';
34
+ } from '../segment/page-slot'
35
35
 
36
36
  /** LRU bound, matching the router's own segment caches. */
37
- const SEGMENT_ENTRY_LIMIT = 64;
37
+ const SEGMENT_ENTRY_LIMIT = 64
38
38
 
39
39
  /**
40
40
  * w9-segment-split: the two derived frames of a body response. The LAYOUT frame
@@ -44,96 +44,96 @@ const SEGMENT_ENTRY_LIMIT = 64;
44
44
  * whose page did not re-fetches `/_layout` alone and composes it with the
45
45
  * cached page frame.
46
46
  */
47
- export const LAYOUT_FRAME_PATH = '/_layout';
48
- export const PAGE_FRAME_PATH = '/_page';
47
+ export const LAYOUT_FRAME_PATH = '/_layout'
48
+ export const PAGE_FRAME_PATH = '/_page'
49
49
 
50
50
  /** The body-segment request key the router sends (`/` is the legacy alias). */
51
- export const BODY_SEGMENT_PATHS = ['/_index', '/', PAGE_FRAME_PATH] as const;
51
+ export const BODY_SEGMENT_PATHS = ['/_index', '/', PAGE_FRAME_PATH] as const
52
52
 
53
53
  export interface SegmentPayloadMeta {
54
54
  /** Segment vary names (may include the `?` sentinel), or null when unknown. */
55
- vary: string[] | null;
55
+ vary: string[] | null
56
56
  /**
57
57
  * w9-segment-split: the LAYOUT frame's own vary set, when the server split
58
58
  * the response. Null/absent means the response was not split and only the
59
59
  * whole-document entry is filed.
60
60
  */
61
- layoutVary?: string[] | null;
61
+ layoutVary?: string[] | null
62
62
  /** w9-segment-split: the PAGE frame's own vary set, when the server split. */
63
- pageVary?: string[] | null;
63
+ pageVary?: string[] | null
64
64
  /** Matched route pattern in colon form, or null. */
65
- route: string | null;
65
+ route: string | null
66
66
  /** Concrete params of the URL that produced the payload. */
67
- params: Record<string, string | string[]>;
67
+ params: Record<string, string | string[]>
68
68
  /**
69
69
  * The route's shared APP SHELL, when the per-URL prerender carried one. Filed
70
70
  * as its own param-independent entry so a navigation to another param of the
71
71
  * route paints instantly.
72
72
  */
73
- shell?: { html: string; route: string };
73
+ shell?: { html: string; route: string }
74
74
  /**
75
75
  * Literal siblings of each DYNAMIC level of the route, keyed by segment index
76
76
  * (the server's `staticChildrenBySegment`). A pathname naming one of them is
77
77
  * served by its own static route, so this payload must never key onto it.
78
78
  */
79
- staticSiblings?: Record<string, string[]>;
79
+ staticSiblings?: Record<string, string[]>
80
80
  }
81
81
 
82
82
  export interface SegmentRecordInput extends SegmentPayloadMeta {
83
83
  /** Segment request path (`/_index`, `/_head`, …). */
84
- segmentPath: string;
84
+ segmentPath: string
85
85
  /** Requested pathname (percent-encoding preserved). */
86
- pathname: string;
86
+ pathname: string
87
87
  /** Requested query string (leading `?` optional). */
88
- search: string;
88
+ search: string
89
89
  /** The payload the router would commit for this segment. */
90
- html: string;
90
+ html: string
91
91
  /** Reuse window in milliseconds. */
92
- staleTimeMs: number;
92
+ staleTimeMs: number
93
93
  /** True when the payload is a whole document, not a truncated shell. */
94
- complete: boolean;
94
+ complete: boolean
95
95
  /** True when the bytes are a pure function of the URL (a prerender). */
96
- static: boolean;
96
+ static: boolean
97
97
  /** True when the payload came from a runtime (request-sampled) prefetch. */
98
- runtime: boolean;
98
+ runtime: boolean
99
99
  /** True when the route's catch-all is optional (`[[...slug]]`). */
100
- catchAllOptional?: boolean;
100
+ catchAllOptional?: boolean
101
101
  }
102
102
 
103
103
  interface SegmentEntry {
104
- key: string;
105
- html: string;
104
+ key: string
105
+ html: string
106
106
  /** The URL this payload was fetched for, canonicalized (`_rsc` stripped). */
107
- url: string;
108
- time: number;
109
- staleTimeMs: number;
110
- complete: boolean;
111
- static: boolean;
112
- runtime: boolean;
107
+ url: string
108
+ time: number
109
+ staleTimeMs: number
110
+ complete: boolean
111
+ static: boolean
112
+ runtime: boolean
113
113
  }
114
114
 
115
115
  export interface SegmentHit {
116
- html: string;
116
+ html: string
117
117
  /** True when this hit may commit a navigation with no network request. */
118
- networkFree: boolean;
118
+ networkFree: boolean
119
119
  /**
120
120
  * True when a PREFETCH of this URL has nothing left to fetch: the entry is fresh and its own vary set
121
121
  * justifies serving this URL. Weaker than `networkFree` - a navigation may still need the dynamic
122
122
  * remainder.
123
123
  */
124
- prefetchSatisfied: boolean;
125
- runtime: boolean;
124
+ prefetchSatisfied: boolean
125
+ runtime: boolean
126
126
  }
127
127
 
128
128
  interface LearnedRoute {
129
- route: string;
130
- vary: string[];
131
- catchAllOptional: boolean;
129
+ route: string
130
+ vary: string[]
131
+ catchAllOptional: boolean
132
132
  /** Literal siblings of the route's dynamic levels, by segment index. */
133
- staticSiblings?: Record<string, string[]>;
133
+ staticSiblings?: Record<string, string[]>
134
134
  }
135
135
 
136
- const entries = new Map<string, SegmentEntry>();
136
+ const entries = new Map<string, SegmentEntry>()
137
137
  /**
138
138
  * Learned route vary sets, per segment path, keyed by route + vary set.
139
139
  *
@@ -144,13 +144,13 @@ const entries = new Map<string, SegmentEntry>();
144
144
  * overwriting each other; `candidateKeys` tries the most specific first.
145
145
  * Insertion order = learn order.
146
146
  */
147
- const learnedRoutes = new Map<string, Map<string, LearnedRoute>>();
147
+ const learnedRoutes = new Map<string, Map<string, LearnedRoute>>()
148
148
  /** pathname -> the route pattern the SERVER resolved it to (sibling guard). */
149
- const resolvedRoutes = new Map<string, string>();
149
+ const resolvedRoutes = new Map<string, string>()
150
150
 
151
151
  /** Wall clock, overridable in tests (the e2e suites mock `Date`). */
152
152
  function now(): number {
153
- return Date.now();
153
+ return Date.now()
154
154
  }
155
155
 
156
156
  // ---------------------------------------------------------------------------
@@ -158,21 +158,21 @@ function now(): number {
158
158
  // ---------------------------------------------------------------------------
159
159
 
160
160
  export function recordSegment(input: SegmentRecordInput): void {
161
- recordOneSegment(input);
161
+ recordOneSegment(input)
162
162
  // w9-segment-split: a body response the server split also files its two
163
163
  // frames, each under its OWN vary set. Additive — the whole-document entry
164
164
  // above is untouched, so nothing that hits today starts missing.
165
165
  if (!BODY_SEGMENT_PATHS.includes(input.segmentPath as (typeof BODY_SEGMENT_PATHS)[number])) {
166
- return;
166
+ return
167
167
  }
168
- if (input.segmentPath === PAGE_FRAME_PATH) return;
168
+ if (input.segmentPath === PAGE_FRAME_PATH) return
169
169
  // The response's layout frame, filed under this URL's EXACT key. Independent of `layoutVary` - the
170
170
  // server publishes that set only when it can prove what the layout read, but a frame keyed on the
171
171
  // URL it was fetched for needs no such proof. BOTH layout-frame records are skipped when the payload
172
172
  // carries no page slot: `stripPageSlotContent` would hand back the whole document, and a "layout
173
173
  // frame" with no slot to splice into makes `needsPageFrameOnly` promise a composition that always
174
174
  // fails - two serial round trips for one navigation.
175
- const framable = pageSlotRange(input.html) !== null;
175
+ const framable = pageSlotRange(input.html) !== null
176
176
  if (framable) {
177
177
  recordOneSegment({
178
178
  ...input,
@@ -184,7 +184,7 @@ export function recordSegment(input: SegmentRecordInput): void {
184
184
  params: {},
185
185
  vary: null,
186
186
  complete: false,
187
- });
187
+ })
188
188
  }
189
189
  if (framable && input.layoutVary) {
190
190
  recordOneSegment({
@@ -195,10 +195,10 @@ export function recordSegment(input: SegmentRecordInput): void {
195
195
  // A layout frame is never the whole page: it may paint, but a navigation
196
196
  // still needs the page below it.
197
197
  complete: false,
198
- });
198
+ })
199
199
  }
200
200
  if (input.pageVary) {
201
- recordOneSegment({ ...input, segmentPath: PAGE_FRAME_PATH, vary: input.pageVary });
201
+ recordOneSegment({ ...input, segmentPath: PAGE_FRAME_PATH, vary: input.pageVary })
202
202
  }
203
203
  }
204
204
 
@@ -215,23 +215,23 @@ export function recordSegment(input: SegmentRecordInput): void {
215
215
  * and that shell IS shared across params by design.
216
216
  */
217
217
  function isRewriteResponse(input: SegmentRecordInput): boolean {
218
- if (!input.route) return false;
218
+ if (!input.route) return false
219
219
  // The catch-all tail is matched in its OPTIONAL form regardless of what the payload declared: a
220
220
  // `[[...slug]]` index response does not always carry `catchAllOptional` on the wire, and reading
221
221
  // its absence as "the URL is a segment short" would mis-file every optional-catchall index as a
222
222
  // rewrite. The guard's bias is to accuse only what it can prove.
223
- const derived = matchRoutePattern(input.route, input.pathname, true);
224
- if (!derived) return true;
223
+ const derived = matchRoutePattern(input.route, input.pathname, true)
224
+ if (!derived) return true
225
225
  return Object.entries(input.params).some(([name, value]) => {
226
- const own = derived[name];
227
- if (own === undefined) return false;
226
+ const own = derived[name]
227
+ if (own === undefined) return false
228
228
  return Array.isArray(value) || Array.isArray(own)
229
229
  ? !Array.isArray(value) ||
230
230
  !Array.isArray(own) ||
231
231
  value.length !== own.length ||
232
232
  value.some((part, index) => part !== own[index])
233
- : value !== own;
234
- });
233
+ : value !== own
234
+ })
235
235
  }
236
236
 
237
237
  function recordOneSegment(rawInput: SegmentRecordInput): void {
@@ -239,48 +239,48 @@ function recordOneSegment(rawInput: SegmentRecordInput): void {
239
239
  // available for the URL it was fetched for and is invisible to every other.
240
240
  const input: SegmentRecordInput = isRewriteResponse(rawInput)
241
241
  ? { ...rawInput, route: null, vary: null }
242
- : rawInput;
242
+ : rawInput
243
243
  if (rawInput.route) {
244
244
  // LRU-bound like `entries`: one record per distinct pathname otherwise
245
245
  // grows without limit (memory-pressure leak-slope test).
246
- resolvedRoutes.delete(input.pathname);
246
+ resolvedRoutes.delete(input.pathname)
247
247
  // The resolved-route record stays TRUE even for a rewrite: the server
248
248
  // really did answer this pathname with that route, and the sibling guard
249
249
  // in `candidateKeys` reads it to keep other routes off this URL.
250
- resolvedRoutes.set(rawInput.pathname, rawInput.route);
250
+ resolvedRoutes.set(rawInput.pathname, rawInput.route)
251
251
  while (resolvedRoutes.size > SEGMENT_ENTRY_LIMIT) {
252
- const oldest = resolvedRoutes.keys().next().value;
253
- if (oldest === undefined) break;
254
- resolvedRoutes.delete(oldest);
252
+ const oldest = resolvedRoutes.keys().next().value
253
+ if (oldest === undefined) break
254
+ resolvedRoutes.delete(oldest)
255
255
  }
256
256
  }
257
257
  const key = entryKey(input.segmentPath, input.route, input.params, input.search, input.vary, {
258
258
  pathname: input.pathname,
259
- });
259
+ })
260
260
  if (input.route && input.vary !== null) {
261
- let byRoute = learnedRoutes.get(input.segmentPath);
261
+ let byRoute = learnedRoutes.get(input.segmentPath)
262
262
  if (!byRoute) {
263
- byRoute = new Map<string, LearnedRoute>();
264
- learnedRoutes.set(input.segmentPath, byRoute);
263
+ byRoute = new Map<string, LearnedRoute>()
264
+ learnedRoutes.set(input.segmentPath, byRoute)
265
265
  }
266
- const learnedKey = `${input.route}${VARY_KEY_SEPARATOR}${[...input.vary].sort().join(',')}`;
266
+ const learnedKey = `${input.route}${VARY_KEY_SEPARATOR}${[...input.vary].sort().join(',')}`
267
267
  // The sibling set is a property of the ROUTE, not of one response: a payload
268
268
  // that omits it (a framed segment truncated above the state script) must not
269
269
  // un-learn what an earlier one published.
270
- const staticSiblings = input.staticSiblings ?? byRoute.get(learnedKey)?.staticSiblings;
270
+ const staticSiblings = input.staticSiblings ?? byRoute.get(learnedKey)?.staticSiblings
271
271
  // Re-inserting moves the route to the end: most recently learned wins on
272
272
  // lookup, mirroring the loading-shell prediction order.
273
- byRoute.delete(learnedKey);
273
+ byRoute.delete(learnedKey)
274
274
  byRoute.set(learnedKey, {
275
275
  route: input.route,
276
276
  vary: input.vary,
277
277
  catchAllOptional: input.catchAllOptional === true,
278
278
  ...(staticSiblings ? { staticSiblings } : {}),
279
- });
279
+ })
280
280
  // LRU-bound like `entries`/`resolvedRoutes`: a route publishing a new vary
281
281
  // set per prefetch (dynamic params in the vary set) would otherwise grow this
282
282
  // map without limit (memory-pressure leak-slope test).
283
- trimLearnedRoutes(byRoute);
283
+ trimLearnedRoutes(byRoute)
284
284
  }
285
285
  entries.set(key, {
286
286
  key,
@@ -291,9 +291,9 @@ function recordOneSegment(rawInput: SegmentRecordInput): void {
291
291
  complete: input.complete,
292
292
  static: input.static,
293
293
  runtime: input.runtime,
294
- });
295
- touch(key);
296
- trim();
294
+ })
295
+ touch(key)
296
+ trim()
297
297
  }
298
298
 
299
299
  // ---------------------------------------------------------------------------
@@ -308,30 +308,30 @@ function recordOneSegment(rawInput: SegmentRecordInput): void {
308
308
  * stage. Everything else is returned for painting only.
309
309
  */
310
310
  export function takeSegment(options: {
311
- pathname: string;
312
- search: string;
313
- segmentPath: string;
311
+ pathname: string
312
+ search: string
313
+ segmentPath: string
314
314
  }): SegmentHit | null {
315
- const requestedUrl = options.pathname + normalizeSegmentSearch(options.search);
315
+ const requestedUrl = options.pathname + normalizeSegmentSearch(options.search)
316
316
  const usable: {
317
- candidate: { key: string; learnedKey?: string; params?: Record<string, string | string[]> };
318
- entry: SegmentEntry;
319
- }[] = [];
317
+ candidate: { key: string; learnedKey?: string; params?: Record<string, string | string[]> }
318
+ entry: SegmentEntry
319
+ }[] = []
320
320
  for (const candidate of candidateKeys(options.segmentPath, options.pathname, options.search)) {
321
- const entry = entries.get(candidate.key);
322
- if (!entry) continue;
321
+ const entry = entries.get(candidate.key)
322
+ if (!entry) continue
323
323
  if (now() - entry.time >= entry.staleTimeMs) {
324
- entries.delete(candidate.key);
325
- continue;
324
+ entries.delete(candidate.key)
325
+ continue
326
326
  }
327
327
  // A shared (different-URL) hit is sound for STATIC content - and for a request-sampled payload
328
328
  // the SERVER's own vary set authorised: a non-exact candidate key exists only because a learned
329
329
  // route published a vary set, and that set is the server's assertion of what the payload depends
330
330
  // on. Everything else stays exact-URL: a request-sampled payload belongs to the URL it sampled.
331
- const shared = entry.url !== requestedUrl;
332
- const varyAuthorised = candidate.learnedKey !== undefined;
333
- if (shared && !entry.static && !(entry.runtime && varyAuthorised)) continue;
334
- usable.push({ candidate, entry });
331
+ const shared = entry.url !== requestedUrl
332
+ const varyAuthorised = candidate.learnedKey !== undefined
333
+ if (shared && !entry.static && !(entry.runtime && varyAuthorised)) continue
334
+ usable.push({ candidate, entry })
335
335
  }
336
336
  // Candidate ORDER is specificity (exact URL first), but an INCOMPLETE first
337
337
  // match yields to a RUNTIME prefetch of this exact URL: an `allow-runtime`
@@ -339,25 +339,26 @@ export function takeSegment(options: {
339
339
  // its Suspense boundaries, while a document-derived entry for the same URL
340
340
  // holds the fallbacks. A complete first match already covers everything a
341
341
  // runtime sample could, so it keeps the established first-match order.
342
- const first = usable[0];
342
+ const first = usable[0]
343
343
  const chosen =
344
344
  first && !first.entry.complete
345
345
  ? (usable.find(({ entry }) => entry.runtime && entry.url === requestedUrl) ?? first)
346
- : first;
346
+ : first
347
347
  if (chosen) {
348
- const { candidate, entry } = chosen;
349
- const key = candidate.key;
350
- const shared = entry.url !== requestedUrl;
351
- touch(key);
348
+ const { candidate, entry } = chosen
349
+ const key = candidate.key
350
+ const shared = entry.url !== requestedUrl
351
+ touch(key)
352
352
  // A learned route that is still serving lookups is live: re-insert it so the
353
353
  // LRU bound evicts the route patterns nobody navigates to.
354
- if (candidate.learnedKey) touchLearnedRoute(options.segmentPath, candidate.learnedKey);
354
+ if (candidate.learnedKey) touchLearnedRoute(options.segmentPath, candidate.learnedKey)
355
355
  return {
356
356
  // A SHARED payload was rendered for a DIFFERENT URL of the same route, so the concrete params
357
357
  // baked into its three param carriers belong to that URL. Re-stamp them for the URL being served -
358
358
  // the client-side mirror of the server's `withRequestRouteParams`, and the same soundness rule:
359
359
  // the vary set authorises sharing the CONTENT, never the param values.
360
- html: shared && candidate.params ? restampSharedParams(entry.html, candidate.params) : entry.html,
360
+ html:
361
+ shared && candidate.params ? restampSharedParams(entry.html, candidate.params) : entry.html,
361
362
  // A COMPLETE + STATIC entry is network-free even when SHARED: a static prerender whose vary set
362
363
  // does not contain the differing param is by construction byte-identical for this URL, so there
363
364
  // is nothing left to fetch. Everything else stays exact-URL only: a shared SHELL (an App Shell
@@ -368,12 +369,11 @@ export function takeSegment(options: {
368
369
  // An exact-URL hit always answers no. A SHARED hit answers no when the sharing was authorised -
369
370
  // either the payload is static, or a published vary set let `candidateKeys` produce the shared
370
371
  // match. The navigation that follows still fetches the dynamic remainder.
371
- prefetchSatisfied:
372
- !shared || (optimisticRoutingEnabled() && (entry.static || entry.runtime)),
372
+ prefetchSatisfied: !shared || (optimisticRoutingEnabled() && (entry.static || entry.runtime)),
373
373
  runtime: entry.runtime,
374
- };
374
+ }
375
375
  }
376
- return null;
376
+ return null
377
377
  }
378
378
 
379
379
  /**
@@ -391,7 +391,7 @@ function optimisticRoutingEnabled(): boolean {
391
391
  return (
392
392
  (globalThis as { __PNEXT_NO_OPTIMISTIC_ROUTING__?: boolean })
393
393
  .__PNEXT_NO_OPTIMISTIC_ROUTING__ !== true
394
- );
394
+ )
395
395
  }
396
396
 
397
397
  /**
@@ -408,24 +408,24 @@ function optimisticRoutingEnabled(): boolean {
408
408
  * and the App-Shell scheduler relies on re-fetching them.
409
409
  */
410
410
  export function segmentPrefetchCovered(options: {
411
- pathname: string;
412
- search: string;
413
- segmentPath: string;
411
+ pathname: string
412
+ search: string
413
+ segmentPath: string
414
414
  }): { html: string } | null {
415
- const requestedUrl = options.pathname + normalizeSegmentSearch(options.search);
415
+ const requestedUrl = options.pathname + normalizeSegmentSearch(options.search)
416
416
  for (const candidate of candidateKeys(options.segmentPath, options.pathname, options.search)) {
417
- const entry = entries.get(candidate.key);
418
- if (!entry) continue;
419
- if (now() - entry.time >= entry.staleTimeMs) continue;
420
- if (entry.runtime) continue;
417
+ const entry = entries.get(candidate.key)
418
+ if (!entry) continue
419
+ if (now() - entry.time >= entry.staleTimeMs) continue
420
+ if (entry.runtime) continue
421
421
  // An exact-URL hit is `takeSegment`'s business (it already dedupes there);
422
422
  // this predicate exists for the SHARED case the vary set authorised.
423
- if (entry.url === requestedUrl || candidate.learnedKey === undefined) continue;
423
+ if (entry.url === requestedUrl || candidate.learnedKey === undefined) continue
424
424
  return {
425
425
  html: candidate.params ? restampSharedParams(entry.html, candidate.params) : entry.html,
426
- };
426
+ }
427
427
  }
428
- return null;
428
+ return null
429
429
  }
430
430
 
431
431
  // ---------------------------------------------------------------------------
@@ -439,14 +439,14 @@ export function segmentPrefetchCovered(options: {
439
439
  * what a navigation PAINTS, and that is the half of the split that has not been
440
440
  * validated end to end. `setSegmentSplitCommit(true)` opts in.
441
441
  */
442
- let segmentSplitCommit = false;
442
+ let segmentSplitCommit = false
443
443
 
444
444
  export function setSegmentSplitCommit(enabled: boolean): void {
445
- segmentSplitCommit = enabled;
445
+ segmentSplitCommit = enabled
446
446
  }
447
447
 
448
448
  export function segmentSplitCommitEnabled(): boolean {
449
- return segmentSplitCommit;
449
+ return segmentSplitCommit
450
450
  }
451
451
 
452
452
  /**
@@ -456,23 +456,23 @@ export function segmentSplitCommitEnabled(): boolean {
456
456
  * param) but hit the page frame, so only the layout has to come off the network.
457
457
  */
458
458
  export function takeComposedSegment(options: {
459
- pathname: string;
460
- search: string;
459
+ pathname: string
460
+ search: string
461
461
  }): SegmentHit | null {
462
- if (!segmentSplitCommit) return null;
463
- const layout = takeSegment({ ...options, segmentPath: LAYOUT_FRAME_PATH });
464
- if (!layout) return null;
465
- const page = takeSegment({ ...options, segmentPath: PAGE_FRAME_PATH });
466
- if (!page) return null;
467
- const html = composeSegmentFrames(layout.html, page.html);
468
- if (html === null) return null;
462
+ if (!segmentSplitCommit) return null
463
+ const layout = takeSegment({ ...options, segmentPath: LAYOUT_FRAME_PATH })
464
+ if (!layout) return null
465
+ const page = takeSegment({ ...options, segmentPath: PAGE_FRAME_PATH })
466
+ if (!page) return null
467
+ const html = composeSegmentFrames(layout.html, page.html)
468
+ if (html === null) return null
469
469
  return {
470
470
  html,
471
471
  // A composed document is only as committable as its weaker half.
472
472
  networkFree: layout.networkFree && page.networkFree,
473
473
  prefetchSatisfied: layout.prefetchSatisfied && page.prefetchSatisfied,
474
474
  runtime: layout.runtime || page.runtime,
475
- };
475
+ }
476
476
  }
477
477
 
478
478
  // ---------------------------------------------------------------------------
@@ -492,34 +492,34 @@ export function takeComposedSegment(options: {
492
492
  * only when the caller says the destination is the current location.
493
493
  */
494
494
  interface DocumentLayoutFrame {
495
- html: string;
496
- time: number;
497
- staleTimeMs: number;
495
+ html: string
496
+ time: number
497
+ staleTimeMs: number
498
498
  }
499
- const documentLayoutFrames = new Map<string, DocumentLayoutFrame>();
499
+ const documentLayoutFrames = new Map<string, DocumentLayoutFrame>()
500
500
  /** Small on purpose: only the live URL's frame is ever read back. */
501
- const DOCUMENT_LAYOUT_LIMIT = 8;
501
+ const DOCUMENT_LAYOUT_LIMIT = 8
502
502
 
503
503
  export function recordDocumentLayoutFrame(input: {
504
- pathname: string;
505
- search: string;
506
- html: string;
507
- staleTimeMs: number;
504
+ pathname: string
505
+ search: string
506
+ html: string
507
+ staleTimeMs: number
508
508
  }): void {
509
509
  // No page slot: `stripPageSlotContent` would return the whole document, and a
510
510
  // composed document would then carry the page twice.
511
- if (pageSlotRange(input.html) === null) return;
512
- const key = input.pathname + normalizeSegmentSearch(input.search);
513
- documentLayoutFrames.delete(key);
511
+ if (pageSlotRange(input.html) === null) return
512
+ const key = input.pathname + normalizeSegmentSearch(input.search)
513
+ documentLayoutFrames.delete(key)
514
514
  documentLayoutFrames.set(key, {
515
515
  html: stripPageSlotContent(input.html),
516
516
  time: now(),
517
517
  staleTimeMs: input.staleTimeMs,
518
- });
518
+ })
519
519
  while (documentLayoutFrames.size > DOCUMENT_LAYOUT_LIMIT) {
520
- const oldest = documentLayoutFrames.keys().next().value;
521
- if (oldest === undefined) break;
522
- documentLayoutFrames.delete(oldest);
520
+ const oldest = documentLayoutFrames.keys().next().value
521
+ if (oldest === undefined) break
522
+ documentLayoutFrames.delete(oldest)
523
523
  }
524
524
  }
525
525
 
@@ -529,24 +529,24 @@ export function recordDocumentLayoutFrame(input: {
529
529
  * document's own frame.
530
530
  */
531
531
  function cachedLayoutFrame(options: {
532
- pathname: string;
533
- search: string;
534
- sameUrl: boolean;
532
+ pathname: string
533
+ search: string
534
+ sameUrl: boolean
535
535
  }): string | null {
536
536
  const hit = takeSegment({
537
537
  pathname: options.pathname,
538
538
  search: options.search,
539
539
  segmentPath: LAYOUT_FRAME_PATH,
540
- });
540
+ })
541
541
  // The continuation chunks are cut at CONSUMPTION time, so every source of a
542
542
  // layout frame — the server's `/_layout` response, a frame derived from a
543
543
  // prefetch, the live document's — is covered by one rule.
544
- if (hit) return stripStreamedContinuation(hit.html);
545
- if (!options.sameUrl) return null;
546
- const frame = documentLayoutFrames.get(options.pathname + normalizeSegmentSearch(options.search));
547
- if (!frame) return null;
548
- if (now() - frame.time >= frame.staleTimeMs) return null;
549
- return stripStreamedContinuation(frame.html);
544
+ if (hit) return stripStreamedContinuation(hit.html)
545
+ if (!options.sameUrl) return null
546
+ const frame = documentLayoutFrames.get(options.pathname + normalizeSegmentSearch(options.search))
547
+ if (!frame) return null
548
+ if (now() - frame.time >= frame.staleTimeMs) return null
549
+ return stripStreamedContinuation(frame.html)
550
550
  }
551
551
 
552
552
  /**
@@ -556,11 +556,11 @@ function cachedLayoutFrame(options: {
556
556
  * `composeCachedLayout`, and any failure there falls back to the whole-document fetch.
557
557
  */
558
558
  export function needsPageFrameOnly(options: {
559
- pathname: string;
560
- search: string;
561
- sameUrl: boolean;
559
+ pathname: string
560
+ search: string
561
+ sameUrl: boolean
562
562
  }): boolean {
563
- return cachedLayoutFrame(options) !== null;
563
+ return cachedLayoutFrame(options) !== null
564
564
  }
565
565
 
566
566
  /**
@@ -569,13 +569,13 @@ export function needsPageFrameOnly(options: {
569
569
  * gate and the response) or the two cannot be proven to line up.
570
570
  */
571
571
  export function composeCachedLayout(options: {
572
- pathname: string;
573
- search: string;
574
- sameUrl: boolean;
575
- pageHtml: string;
572
+ pathname: string
573
+ search: string
574
+ sameUrl: boolean
575
+ pageHtml: string
576
576
  }): string | null {
577
- const layout = cachedLayoutFrame(options);
578
- return layout === null ? null : composeSegmentFrames(layout, options.pageHtml);
577
+ const layout = cachedLayoutFrame(options)
578
+ return layout === null ? null : composeSegmentFrames(layout, options.pageHtml)
579
579
  }
580
580
 
581
581
  /**
@@ -586,17 +586,17 @@ export function composeCachedLayout(options: {
586
586
  * two cannot be proven to line up.
587
587
  */
588
588
  export function composeFetchedLayoutFrame(options: {
589
- pathname: string;
590
- search: string;
591
- layoutHtml: string;
589
+ pathname: string
590
+ search: string
591
+ layoutHtml: string
592
592
  }): string | null {
593
593
  const page = takeSegment({
594
594
  pathname: options.pathname,
595
595
  search: options.search,
596
596
  segmentPath: PAGE_FRAME_PATH,
597
- });
598
- if (!page) return null;
599
- return composeSegmentFrames(stripStreamedContinuation(options.layoutHtml), page.html);
597
+ })
598
+ if (!page) return null
599
+ return composeSegmentFrames(stripStreamedContinuation(options.layoutHtml), page.html)
600
600
  }
601
601
 
602
602
  /**
@@ -609,30 +609,24 @@ export function composeFetchedLayoutFrame(options: {
609
609
  * params-dependent content sits behind a Suspense boundary and resolves in the dynamic stage), so once
610
610
  * the page frame's vary set covers the URL the prefetch has nothing left to fetch.
611
611
  */
612
- export function runtimePageFrameSatisfied(options: {
613
- pathname: string;
614
- search: string;
615
- }): boolean {
616
- const requestedUrl = options.pathname + normalizeSegmentSearch(options.search);
612
+ export function runtimePageFrameSatisfied(options: { pathname: string; search: string }): boolean {
613
+ const requestedUrl = options.pathname + normalizeSegmentSearch(options.search)
617
614
  for (const candidate of candidateKeys(PAGE_FRAME_PATH, options.pathname, options.search)) {
618
- const entry = entries.get(candidate.key);
619
- if (!entry?.runtime) continue;
620
- if (now() - entry.time >= entry.staleTimeMs) continue;
615
+ const entry = entries.get(candidate.key)
616
+ if (!entry?.runtime) continue
617
+ if (now() - entry.time >= entry.staleTimeMs) continue
621
618
  // SHARED coverage only: a URL the client fetched for itself keeps its own
622
619
  // prefetch (a stale/incomplete entry of that URL must still refetch); this
623
620
  // rule exists for the sibling param the vary set already covers.
624
- if (entry.url === requestedUrl || candidate.learnedKey === undefined) continue;
625
- return true;
621
+ if (entry.url === requestedUrl || candidate.learnedKey === undefined) continue
622
+ return true
626
623
  }
627
- return false;
624
+ return false
628
625
  }
629
626
 
630
- export function needsLayoutFrameOnly(options: {
631
- pathname: string;
632
- search: string;
633
- }): boolean {
634
- if (takeSegment({ ...options, segmentPath: LAYOUT_FRAME_PATH })) return false;
635
- return takeSegment({ ...options, segmentPath: PAGE_FRAME_PATH }) !== null;
627
+ export function needsLayoutFrameOnly(options: { pathname: string; search: string }): boolean {
628
+ if (takeSegment({ ...options, segmentPath: LAYOUT_FRAME_PATH })) return false
629
+ return takeSegment({ ...options, segmentPath: PAGE_FRAME_PATH }) !== null
636
630
  }
637
631
 
638
632
  /**
@@ -650,29 +644,29 @@ function candidateKeys(
650
644
  ): { key: string; learnedKey?: string; params?: Record<string, string | string[]> }[] {
651
645
  const keys: { key: string; learnedKey?: string; params?: Record<string, string | string[]> }[] = [
652
646
  { key: entryKey(segmentPath, null, {}, search, null, { pathname }) },
653
- ];
654
- const byRoute = learnedRoutes.get(segmentPath);
655
- if (!byRoute) return keys;
656
- const ownRoute = resolvedRoutes.get(pathname);
647
+ ]
648
+ const byRoute = learnedRoutes.get(segmentPath)
649
+ if (!byRoute) return keys
650
+ const ownRoute = resolvedRoutes.get(pathname)
657
651
  const learned = [...byRoute.entries()]
658
652
  .reverse()
659
653
  .filter(([, candidate]) => ownRoute === undefined || candidate.route === ownRoute)
660
654
  // Stable sort: within one specificity the most recently learned wins.
661
- .sort(([, a], [, b]) => b.vary.length - a.vary.length);
655
+ .sort(([, a], [, b]) => b.vary.length - a.vary.length)
662
656
  for (const [learnedKey, candidate] of learned) {
663
- const params = matchRoutePattern(candidate.route, pathname, candidate.catchAllOptional);
664
- if (!params) continue;
657
+ const params = matchRoutePattern(candidate.route, pathname, candidate.catchAllOptional)
658
+ if (!params) continue
665
659
  // The pattern matches, but the segment it matched is a literal the server
666
660
  // published as a STATIC SIBLING of this dynamic level: that sibling's own
667
661
  // route serves the URL, so the dynamic route's shared bytes are not its.
668
- if (namesStaticSibling(pathname, candidate.staticSiblings)) continue;
662
+ if (namesStaticSibling(pathname, candidate.staticSiblings)) continue
669
663
  keys.push({
670
664
  key: entryKey(segmentPath, candidate.route, params, search, candidate.vary, { pathname }),
671
665
  learnedKey,
672
666
  params,
673
- });
667
+ })
674
668
  }
675
- return keys;
669
+ return keys
676
670
  }
677
671
 
678
672
  function entryKey(
@@ -690,7 +684,7 @@ function entryKey(
690
684
  search,
691
685
  vary: route ? vary : null,
692
686
  pathname: context.pathname,
693
- });
687
+ })
694
688
  }
695
689
 
696
690
  // ---------------------------------------------------------------------------
@@ -707,25 +701,25 @@ export function matchRoutePattern(
707
701
  pathname: string,
708
702
  optionalCatchAll = false,
709
703
  ): Record<string, string | string[]> | null {
710
- const patternParts = pattern.split('/').filter(Boolean);
711
- const pathParts = pathname.split('/').filter(Boolean);
712
- const params: Record<string, string | string[]> = {};
713
- let cursor = 0;
704
+ const patternParts = pattern.split('/').filter(Boolean)
705
+ const pathParts = pathname.split('/').filter(Boolean)
706
+ const params: Record<string, string | string[]> = {}
707
+ let cursor = 0
714
708
  for (const part of patternParts) {
715
- const catchAll = /^:([\w$]+)\*$/.exec(part);
709
+ const catchAll = /^:([\w$]+)\*$/.exec(part)
716
710
  if (catchAll) {
717
- const rest = pathParts.slice(cursor).map(decodeSegment);
718
- if (rest.length === 0 && !optionalCatchAll) return null;
719
- if (rest.length > 0) params[catchAll[1]!] = rest;
720
- return params;
711
+ const rest = pathParts.slice(cursor).map(decodeSegment)
712
+ if (rest.length === 0 && !optionalCatchAll) return null
713
+ if (rest.length > 0) params[catchAll[1]!] = rest
714
+ return params
721
715
  }
722
- const dynamic = /^:([\w$]+)$/.exec(part);
723
- const value = pathParts[cursor++];
724
- if (value === undefined) return null;
725
- if (dynamic) params[dynamic[1]!] = decodeSegment(value);
726
- else if (part !== value) return null;
716
+ const dynamic = /^:([\w$]+)$/.exec(part)
717
+ const value = pathParts[cursor++]
718
+ if (value === undefined) return null
719
+ if (dynamic) params[dynamic[1]!] = decodeSegment(value)
720
+ else if (part !== value) return null
727
721
  }
728
- return cursor === pathParts.length ? params : null;
722
+ return cursor === pathParts.length ? params : null
729
723
  }
730
724
 
731
725
  // ---------------------------------------------------------------------------
@@ -733,8 +727,8 @@ export function matchRoutePattern(
733
727
  // ---------------------------------------------------------------------------
734
728
 
735
729
  /** The island prop placeholder the server fills per request (slots.tsx). */
736
- const PROMISE_MARKER_KEY = '__pnextPromise';
737
- const ISLAND_PARAMS_ATTRIBUTE = 'data-pnext-params';
730
+ const PROMISE_MARKER_KEY = '__pnextPromise'
731
+ const ISLAND_PARAMS_ATTRIBUTE = 'data-pnext-params'
738
732
 
739
733
  /**
740
734
  * Rewrite every param carrier in a payload rendered for a SIBLING URL of the
@@ -753,40 +747,40 @@ const ISLAND_PARAMS_ATTRIBUTE = 'data-pnext-params';
753
747
  * content that merely happens to contain the same string is untouched.
754
748
  */
755
749
  function restampSharedParams(html: string, params: Record<string, string | string[]>): string {
756
- const match = /(window\.__PNEXT_ROUTE__=)(\{.*?\})(;?<\/script>)/s.exec(html);
757
- if (!match?.[2]) return html;
758
- let state: Record<string, unknown>;
750
+ const match = /(window\.__PNEXT_ROUTE__=)(\{.*?\})(;?<\/script>)/s.exec(html)
751
+ if (!match?.[2]) return html
752
+ let state: Record<string, unknown>
759
753
  try {
760
- state = JSON.parse(match[2]) as Record<string, unknown>;
754
+ state = JSON.parse(match[2]) as Record<string, unknown>
761
755
  } catch {
762
756
  // A route script we cannot parse cannot be re-stamped safely.
763
- return html;
757
+ return html
764
758
  }
765
- const baked = (state.params ?? {}) as Record<string, string | string[]>;
766
- const bakedJson = paramsJson(baked);
767
- const targetJson = paramsJson(params);
768
- if (bakedJson === targetJson) return html;
759
+ const baked = (state.params ?? {}) as Record<string, string | string[]>
760
+ const bakedJson = paramsJson(baked)
761
+ const targetJson = paramsJson(params)
762
+ if (bakedJson === targetJson) return html
769
763
  const rewritten =
770
764
  html.slice(0, match.index) +
771
765
  `${match[1]}${paramsJson({ ...state, params })}${match[3]}` +
772
- html.slice(match.index + match[0].length);
766
+ html.slice(match.index + match[0].length)
773
767
  const replacements: [string, string][] = [
774
768
  [islandParamsAttribute(bakedJson), islandParamsAttribute(targetJson)],
775
769
  [promiseMarkerJson(bakedJson), promiseMarkerJson(targetJson)],
776
- ];
770
+ ]
777
771
  return rewriteIslandMarkup(rewritten, text => {
778
- let next = text;
772
+ let next = text
779
773
  for (const [from, to] of replacements) {
780
- next = next.split(from).join(to).split(escapeAttribute(from)).join(escapeAttribute(to));
774
+ next = next.split(from).join(to).split(escapeAttribute(from)).join(escapeAttribute(to))
781
775
  }
782
- return next;
783
- });
776
+ return next
777
+ })
784
778
  }
785
779
 
786
780
  const islandParamsAttribute = (json: string) =>
787
- `${ISLAND_PARAMS_ATTRIBUTE}="${json.replaceAll('"', '&quot;')}"`;
788
- const promiseMarkerJson = (json: string) => `{"${PROMISE_MARKER_KEY}":${json}}`;
789
- const escapeAttribute = (json: string) => json.replaceAll('"', '&quot;');
781
+ `${ISLAND_PARAMS_ATTRIBUTE}="${json.replaceAll('"', '&quot;')}"`
782
+ const promiseMarkerJson = (json: string) => `{"${PROMISE_MARKER_KEY}":${json}}`
783
+ const escapeAttribute = (json: string) => json.replaceAll('"', '&quot;')
790
784
 
791
785
  /** `serializeProps`' output shape, mirrored so the strings compare byte-wise. */
792
786
  const JSON_ESCAPES: Record<string, string> = {
@@ -795,10 +789,10 @@ const JSON_ESCAPES: Record<string, string> = {
795
789
  '&': '\\u0026',
796
790
  '\u2028': '\\u2028',
797
791
  '\u2029': '\\u2029',
798
- };
792
+ }
799
793
 
800
794
  function paramsJson(value: unknown): string {
801
- return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, char => JSON_ESCAPES[char] ?? char);
795
+ return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, char => JSON_ESCAPES[char] ?? char)
802
796
  }
803
797
 
804
798
  /**
@@ -810,21 +804,21 @@ function rewriteIslandMarkup(html: string, rewrite: (text: string) => string): s
810
804
  return rewrite(html).replace(
811
805
  /<!--pnext-(client|client-after|page):([^>]*)-->/g,
812
806
  (marker, kind: string, encoded: string) => {
813
- const decoded = encoded.replaceAll('&lt;', '<').replaceAll('&gt;', '>');
814
- const next = rewrite(decoded);
807
+ const decoded = encoded.replaceAll('&lt;', '<').replaceAll('&gt;', '>')
808
+ const next = rewrite(decoded)
815
809
  return next === decoded
816
810
  ? marker
817
- : `<!--pnext-${kind}:${next.replaceAll('<', '&lt;').replaceAll('>', '&gt;')}-->`;
811
+ : `<!--pnext-${kind}:${next.replaceAll('<', '&lt;').replaceAll('>', '&gt;')}-->`
818
812
  },
819
- );
813
+ )
820
814
  }
821
815
 
822
816
  function decodeSegment(segment: string): string {
823
817
  try {
824
- return decodeURIComponent(segment);
818
+ return decodeURIComponent(segment)
825
819
  } catch {
826
820
  // A malformed escape is the route-cache identity as written; keep it.
827
- return segment;
821
+ return segment
828
822
  }
829
823
  }
830
824
 
@@ -840,57 +834,57 @@ function decodeSegment(segment: string): string {
840
834
  export function evictPageSegments(): void {
841
835
  for (const key of [...entries.keys()]) {
842
836
  if (BODY_SEGMENT_PATHS.some(path => key.startsWith(path + VARY_KEY_SEPARATOR))) {
843
- entries.delete(key);
837
+ entries.delete(key)
844
838
  }
845
839
  }
846
840
  // The vary sets learned for those page segments describe payloads that are
847
841
  // gone; keeping them would leak one map per route across refreshes.
848
- for (const path of BODY_SEGMENT_PATHS) learnedRoutes.delete(path);
842
+ for (const path of BODY_SEGMENT_PATHS) learnedRoutes.delete(path)
849
843
  }
850
844
 
851
845
  /** Drop every entry (a revalidation invalidates the whole segment cache). */
852
846
  export function clearSegmentCache(): void {
853
- entries.clear();
854
- learnedRoutes.clear();
855
- resolvedRoutes.clear();
847
+ entries.clear()
848
+ learnedRoutes.clear()
849
+ resolvedRoutes.clear()
856
850
  // A revalidation invalidates the layouts too: a `/_page`-only navigation
857
851
  // after one would keep painting the pre-revalidation layout chain.
858
- documentLayoutFrames.clear();
852
+ documentLayoutFrames.clear()
859
853
  }
860
854
 
861
855
  /** Test seam: the number of live entries. */
862
856
  export function segmentEntryCount(): number {
863
- return entries.size;
857
+ return entries.size
864
858
  }
865
859
 
866
860
  function touch(key: string): void {
867
- const entry = entries.get(key);
868
- if (!entry) return;
869
- entries.delete(key);
870
- entries.set(key, entry);
861
+ const entry = entries.get(key)
862
+ if (!entry) return
863
+ entries.delete(key)
864
+ entries.set(key, entry)
871
865
  }
872
866
 
873
867
  function trim(): void {
874
868
  while (entries.size > SEGMENT_ENTRY_LIMIT) {
875
- const oldest = entries.keys().next().value;
876
- if (oldest === undefined) return;
877
- entries.delete(oldest);
869
+ const oldest = entries.keys().next().value
870
+ if (oldest === undefined) return
871
+ entries.delete(oldest)
878
872
  }
879
873
  }
880
874
 
881
875
  function touchLearnedRoute(segmentPath: string, learnedKey: string): void {
882
- const byRoute = learnedRoutes.get(segmentPath);
883
- const learned = byRoute?.get(learnedKey);
884
- if (!byRoute || !learned) return;
885
- byRoute.delete(learnedKey);
886
- byRoute.set(learnedKey, learned);
876
+ const byRoute = learnedRoutes.get(segmentPath)
877
+ const learned = byRoute?.get(learnedKey)
878
+ if (!byRoute || !learned) return
879
+ byRoute.delete(learnedKey)
880
+ byRoute.set(learnedKey, learned)
887
881
  }
888
882
 
889
883
  function trimLearnedRoutes(byRoute: Map<string, LearnedRoute>): void {
890
884
  while (byRoute.size > SEGMENT_ENTRY_LIMIT) {
891
- const oldest = byRoute.keys().next().value;
892
- if (oldest === undefined) return;
893
- byRoute.delete(oldest);
885
+ const oldest = byRoute.keys().next().value
886
+ if (oldest === undefined) return
887
+ byRoute.delete(oldest)
894
888
  }
895
889
  }
896
890
 
@@ -903,12 +897,12 @@ function trimLearnedRoutes(byRoute: Map<string, LearnedRoute>): void {
903
897
  * sibling pattern.
904
898
  */
905
899
  export function resolvedRouteFor(pathname: string): string | null {
906
- return resolvedRoutes.get(pathname) ?? null;
900
+ return resolvedRoutes.get(pathname) ?? null
907
901
  }
908
902
 
909
903
  /** Test seam: how many route/vary sets are remembered for a segment path. */
910
904
  export function learnedRouteCount(segmentPath: string): number {
911
- return learnedRoutes.get(segmentPath)?.size ?? 0;
905
+ return learnedRoutes.get(segmentPath)?.size ?? 0
912
906
  }
913
907
 
914
908
  // ---------------------------------------------------------------------------
@@ -923,24 +917,24 @@ export function decodeSegmentPayload(
923
917
  body: string,
924
918
  expectedSegment: string,
925
919
  ): (SegmentPayloadMeta & { html: string }) | null {
926
- const json = body.startsWith('0:') ? body.slice(2) : body;
920
+ const json = body.startsWith('0:') ? body.slice(2) : body
927
921
  let payload: {
928
- segment?: unknown;
929
- html?: unknown;
930
- vary?: unknown;
931
- layoutVary?: unknown;
932
- pageVary?: unknown;
933
- route?: unknown;
934
- params?: unknown;
935
- shell?: unknown;
936
- };
922
+ segment?: unknown
923
+ html?: unknown
924
+ vary?: unknown
925
+ layoutVary?: unknown
926
+ pageVary?: unknown
927
+ route?: unknown
928
+ params?: unknown
929
+ shell?: unknown
930
+ }
937
931
  try {
938
- payload = JSON.parse(json) as typeof payload;
932
+ payload = JSON.parse(json) as typeof payload
939
933
  } catch {
940
- return null;
934
+ return null
941
935
  }
942
- if (payload.segment !== expectedSegment || typeof payload.html !== 'string') return null;
943
- const payloadSiblings = staticSiblingsFromPayloadHtml(payload.html);
936
+ if (payload.segment !== expectedSegment || typeof payload.html !== 'string') return null
937
+ const payloadSiblings = staticSiblingsFromPayloadHtml(payload.html)
944
938
  return {
945
939
  html: payload.html,
946
940
  vary: Array.isArray(payload.vary) ? payload.vary.map(String) : null,
@@ -952,14 +946,14 @@ export function decodeSegmentPayload(
952
946
  params: normalizeWireParams(payload.params),
953
947
  ...(payloadSiblings ? { staticSiblings: payloadSiblings } : {}),
954
948
  ...(appShellOf(payload.shell) ? { shell: appShellOf(payload.shell)! } : {}),
955
- };
949
+ }
956
950
  }
957
951
 
958
952
  /** The `shell` field of a segment payload, when it carries a usable one. */
959
953
  function appShellOf(value: unknown): { html: string; route: string } | null {
960
- const shell = value as { html?: unknown; route?: unknown } | null | undefined;
961
- if (!shell || typeof shell.html !== 'string' || typeof shell.route !== 'string') return null;
962
- return { html: shell.html, route: shell.route };
954
+ const shell = value as { html?: unknown; route?: unknown } | null | undefined
955
+ if (!shell || typeof shell.html !== 'string' || typeof shell.route !== 'string') return null
956
+ return { html: shell.html, route: shell.route }
963
957
  }
964
958
 
965
959
  /**
@@ -975,33 +969,33 @@ function appShellOf(value: unknown): { html: string; route: string } | null {
975
969
  * it a hard load of `/route/foo` seeds an entry no navigation to `/route/bar` can key onto.
976
970
  */
977
971
  export function decodeDocumentSegmentMeta(html: string): SegmentPayloadMeta {
978
- const state = documentRouteState(html);
979
- if (!state) return { vary: null, route: null, params: {} };
980
- const siblings = staticSiblingsOf(state);
972
+ const state = documentRouteState(html)
973
+ if (!state) return { vary: null, route: null, params: {} }
974
+ const siblings = staticSiblingsOf(state)
981
975
  return {
982
976
  vary: Array.isArray(state.pageVary) ? state.pageVary.map(String) : null,
983
977
  route: typeof state.route === 'string' ? state.route : null,
984
978
  params: normalizeWireParams(state.params),
985
979
  ...(siblings ? { staticSiblings: siblings } : {}),
986
- };
980
+ }
987
981
  }
988
982
 
989
983
  interface WireRouteState {
990
- route?: unknown;
991
- params?: unknown;
992
- pageVary?: unknown;
993
- staticChildren?: unknown;
994
- staticChildrenBySegment?: unknown;
984
+ route?: unknown
985
+ params?: unknown
986
+ pageVary?: unknown
987
+ staticChildren?: unknown
988
+ staticChildrenBySegment?: unknown
995
989
  }
996
990
 
997
991
  /** The `window.__PNEXT_ROUTE__` state embedded in a rendered document. */
998
992
  function documentRouteState(html: string): WireRouteState | null {
999
- const match = /window\.__PNEXT_ROUTE__=(\{.*?\});?<\/script>/s.exec(html);
1000
- if (!match?.[1]) return null;
993
+ const match = /window\.__PNEXT_ROUTE__=(\{.*?\});?<\/script>/s.exec(html)
994
+ if (!match?.[1]) return null
1001
995
  try {
1002
- return JSON.parse(match[1]) as WireRouteState;
996
+ return JSON.parse(match[1]) as WireRouteState
1003
997
  } catch {
1004
- return null;
998
+ return null
1005
999
  }
1006
1000
  }
1007
1001
 
@@ -1011,27 +1005,27 @@ function documentRouteState(html: string): WireRouteState | null {
1011
1005
  * deepest-level `staticChildren` at the pattern's last dynamic index).
1012
1006
  */
1013
1007
  function staticSiblingsOf(state: WireRouteState): Record<string, string[]> | undefined {
1014
- const siblings: Record<string, string[]> = {};
1015
- const bySegment = state.staticChildrenBySegment;
1008
+ const siblings: Record<string, string[]> = {}
1009
+ const bySegment = state.staticChildrenBySegment
1016
1010
  if (typeof bySegment === 'object' && bySegment !== null) {
1017
1011
  for (const [index, names] of Object.entries(bySegment as Record<string, unknown>)) {
1018
- if (Array.isArray(names)) siblings[index] = names.map(String);
1012
+ if (Array.isArray(names)) siblings[index] = names.map(String)
1019
1013
  }
1020
1014
  }
1021
1015
  if (Array.isArray(state.staticChildren) && typeof state.route === 'string') {
1022
- const parts = state.route.split('/').filter(Boolean);
1023
- const deepest = parts.reduce((last, part, index) => (part.startsWith(':') ? index : last), -1);
1016
+ const parts = state.route.split('/').filter(Boolean)
1017
+ const deepest = parts.reduce((last, part, index) => (part.startsWith(':') ? index : last), -1)
1024
1018
  if (deepest >= 0 && siblings[String(deepest)] === undefined) {
1025
- siblings[String(deepest)] = state.staticChildren.map(String);
1019
+ siblings[String(deepest)] = state.staticChildren.map(String)
1026
1020
  }
1027
1021
  }
1028
- return Object.keys(siblings).length > 0 ? siblings : undefined;
1022
+ return Object.keys(siblings).length > 0 ? siblings : undefined
1029
1023
  }
1030
1024
 
1031
1025
  /** A framed segment payload's static siblings, read off the HTML it carries. */
1032
1026
  function staticSiblingsFromPayloadHtml(html: string): Record<string, string[]> | undefined {
1033
- const state = documentRouteState(html);
1034
- return state ? staticSiblingsOf(state) : undefined;
1027
+ const state = documentRouteState(html)
1028
+ return state ? staticSiblingsOf(state) : undefined
1035
1029
  }
1036
1030
 
1037
1031
  /**
@@ -1048,30 +1042,30 @@ function namesStaticSibling(
1048
1042
  pathname: string,
1049
1043
  siblings: Record<string, string[]> | undefined,
1050
1044
  ): boolean {
1051
- if (!siblings) return false;
1052
- const parts = pathname.split('/').filter(Boolean).map(decodeSegment);
1045
+ if (!siblings) return false
1046
+ const parts = pathname.split('/').filter(Boolean).map(decodeSegment)
1053
1047
  return Object.entries(siblings).some(([index, names]) => {
1054
- const segment = parts[Number(index)];
1055
- return segment !== undefined && names.includes(segment);
1056
- });
1048
+ const segment = parts[Number(index)]
1049
+ return segment !== undefined && names.includes(segment)
1050
+ })
1057
1051
  }
1058
1052
 
1059
1053
  function normalizeWireParams(value: unknown): Record<string, string | string[]> {
1060
- if (typeof value !== 'object' || value === null) return {};
1061
- const params: Record<string, string | string[]> = {};
1054
+ if (typeof value !== 'object' || value === null) return {}
1055
+ const params: Record<string, string | string[]> = {}
1062
1056
  for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {
1063
- if (Array.isArray(raw)) params[key] = (raw as unknown[]).map(stringifyParam);
1064
- else if (raw !== undefined && raw !== null) params[key] = stringifyParam(raw);
1057
+ if (Array.isArray(raw)) params[key] = (raw as unknown[]).map(stringifyParam)
1058
+ else if (raw !== undefined && raw !== null) params[key] = stringifyParam(raw)
1065
1059
  }
1066
- return params;
1060
+ return params
1067
1061
  }
1068
1062
 
1069
1063
  /** Wire params are strings; anything else is coerced defensively. */
1070
1064
  function stringifyParam(value: unknown): string {
1071
- return typeof value === 'string' ? value : JSON.stringify(value) ?? '';
1065
+ return typeof value === 'string' ? value : (JSON.stringify(value) ?? '')
1072
1066
  }
1073
1067
 
1074
1068
  /** True when a decoded vary set makes the segment query-dependent. */
1075
1069
  export function varyIncludesSearch(vary: readonly string[] | null): boolean {
1076
- return vary?.includes(SEARCH_PARAMS_SENTINEL) === true;
1070
+ return vary?.includes(SEARCH_PARAMS_SENTINEL) === true
1077
1071
  }