@wular/pnext 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,679 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { basename, join, relative, sep } from 'node:path';
3
+ import { h, type ComponentChildren, type ComponentType } from 'preact';
4
+ import type { RouteParamsSnapshot } from './island-context';
5
+ import { Suspense } from '../api/suspense';
6
+ import type { ClientReference } from '../client/reference';
7
+ import {
8
+ consumedSegments,
9
+ isGroupSegment,
10
+ isSlotSegment,
11
+ matchSegments,
12
+ matchSlotInterceptor,
13
+ pathnameSegments,
14
+ slotConventionFile,
15
+ slotDirectoriesIn,
16
+ type SegmentMatch,
17
+ } from '../routing/slots';
18
+ import { toPosixPath } from '../utils/fs';
19
+ import type {
20
+ PageProps,
21
+ RouteManifestEntry,
22
+ RouteParamValue,
23
+ ServerComponent,
24
+ } from '../types';
25
+
26
+ interface SlotRenderOptions {
27
+ config: { appPath: string };
28
+ url: URL;
29
+ nav?: {
30
+ soft?: boolean;
31
+ childrenPath?: string;
32
+ targetPath?: string;
33
+ state?: { childrenSearch?: string; slots?: Record<string, string> };
34
+ };
35
+ route: Pick<RouteManifestEntry, 'clientReferences' | 'slotDirs'>;
36
+ params?: Record<string, RouteParamValue>;
37
+ clientComponents?: Map<string, ComponentType<Record<string, unknown>>>;
38
+ slotStateOut?: Record<string, string>;
39
+ moduleLoader?: (file: string) => Promise<Record<string, unknown>> | Record<string, unknown>;
40
+ }
41
+
42
+ interface ServerVNodeProps extends Record<string, unknown> {
43
+ children?: ComponentChildren;
44
+ }
45
+
46
+ interface PageModule {
47
+ default?: ServerComponent<PageProps>;
48
+ }
49
+
50
+ interface LayoutModule {
51
+ default?: ServerComponent<{ children: ComponentChildren }>;
52
+ }
53
+
54
+ interface LoadingModule {
55
+ default?: ServerComponent<Record<string, never>>;
56
+ }
57
+
58
+ interface SlotRenderDeps<Options extends SlotRenderOptions> {
59
+ importModule: (file: string, options: Options) => Promise<Record<string, unknown>>;
60
+ markServerReference: <T>(component: T) => T;
61
+ clientReferenceStub: (reference: ClientReference) => ComponentType<Record<string, unknown>>;
62
+ createPageProps: (options: Options) => PageProps;
63
+ }
64
+
65
+ export interface SlotContext<Options extends SlotRenderOptions = SlotRenderOptions> {
66
+ options: Options;
67
+ deps: SlotRenderDeps<Options>;
68
+ appPath: string;
69
+ urlPath: string;
70
+ urlSegments: string[];
71
+ /** The request's search string (`?a=b` or ''), `_rsc` union query stripped. */
72
+ urlSearch: string;
73
+ childrenPath: string;
74
+ childrenSegments: string[];
75
+ /** Search string the children tree renders with (host renders inherit it). */
76
+ childrenSearch: string;
77
+ soft: boolean;
78
+ statePaths: Record<string, string>;
79
+ stateOut: Record<string, string>;
80
+ matches: ResolvedSlotMatch[];
81
+ }
82
+
83
+ export interface ResolvedSlotMatch {
84
+ slotDir: string;
85
+ pageFile?: string;
86
+ layoutFiles: string[];
87
+ params: Record<string, RouteParamValue>;
88
+ }
89
+
90
+ export function createSlotContext<Options extends SlotRenderOptions>(
91
+ options: Options,
92
+ deps: SlotRenderDeps<Options>,
93
+ ): SlotContext<Options> {
94
+ const urlPath = trimPathname(options.nav?.targetPath ?? options.url.pathname);
95
+ const childrenPath = trimPathname(options.nav?.childrenPath ?? options.url.pathname);
96
+ const urlSearch = stripRscSearch(options.url.search);
97
+ // A host render (children anchored away from the URL) keeps the children
98
+ // tree's own recorded query; a direct render's children query IS the URL's.
99
+ const childrenSearch =
100
+ options.nav?.soft && childrenPath !== trimPathname(options.url.pathname)
101
+ ? (options.nav?.state?.childrenSearch ?? '')
102
+ : urlSearch;
103
+ return {
104
+ options,
105
+ deps,
106
+ appPath: options.config.appPath,
107
+ urlPath,
108
+ urlSegments: pathnameSegments(urlPath),
109
+ urlSearch,
110
+ childrenPath,
111
+ childrenSegments: pathnameSegments(childrenPath),
112
+ childrenSearch,
113
+ soft: Boolean(options.nav?.soft),
114
+ statePaths: options.nav?.state?.slots ?? {},
115
+ stateOut: options.slotStateOut ?? {},
116
+ matches: [],
117
+ };
118
+ }
119
+
120
+ function trimPathname(pathname: string) {
121
+ return pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname;
122
+ }
123
+
124
+ /** Split a recorded slot source (`pathname` or `pathname?query`) into parts. */
125
+ function sourceParts(source: string): { path: string; search: string } {
126
+ const queryIndex = source.indexOf('?');
127
+ if (queryIndex === -1) return { path: trimPathname(source), search: '' };
128
+ return {
129
+ path: trimPathname(source.slice(0, queryIndex)),
130
+ search: source.slice(queryIndex),
131
+ };
132
+ }
133
+
134
+ /** The router's `_rsc` union query is a CDN cache key, never an app param. */
135
+ function stripRscSearch(search: string): string {
136
+ if (!search.includes('_rsc')) return search;
137
+ const params = new URLSearchParams(search);
138
+ params.delete('_rsc');
139
+ const stripped = params.toString();
140
+ return stripped ? `?${stripped}` : '';
141
+ }
142
+
143
+ /** Render every `@slot` in `dir` into layout props. */
144
+ export async function renderDirSlots<Options extends SlotRenderOptions>(
145
+ context: SlotContext<Options>,
146
+ dir: string,
147
+ ): Promise<Record<string, ComponentChildren>> {
148
+ const slotDirs = slotDirectoriesIn(dir);
149
+ if (slotDirs.length === 0) return {};
150
+ const consumed = consumedSegments(context.appPath, dir);
151
+ // A `[...catchAll]` layout greedily consumes every remaining URL segment (a catch-all is always the last
152
+ // route segment), so any `@slot` it hosts matches at the index - the empty remaining path. Treat Infinity
153
+ // as "all segments consumed" rather than skipping slot rendering, which would drop the slot entirely on a
154
+ // catch-all route.
155
+ const effectiveConsumed = Number.isFinite(consumed)
156
+ ? consumed
157
+ : context.childrenSegments.length;
158
+ const props: Record<string, ComponentChildren> = {};
159
+ for (const slotDir of slotDirs) {
160
+ const content = await resolveSlotContent(context, slotDir, effectiveConsumed);
161
+ // An unmatched slot with no `default.*` stays a bare null: wrapping it in
162
+ // the boundary marker would make it a truthy VNode, and an unmatched
163
+ // `@children` slot must stay falsy so childrenFromSlots can fall back to
164
+ // the segment-matched children tree.
165
+ props[basename(slotDir).slice(1)] = content == null ? null : wrapSlotBoundary(content);
166
+ }
167
+ return props;
168
+ }
169
+
170
+ async function resolveSlotContent<Options extends SlotRenderOptions>(
171
+ context: SlotContext<Options>,
172
+ slotDir: string,
173
+ consumed: number,
174
+ ): Promise<ComponentChildren> {
175
+ const key = toPosixPath(relative(context.appPath, slotDir));
176
+
177
+ // Interceptor targets are absolute paths, so the navigated URL applies to
178
+ // them regardless of the chain's consumed prefix (that mismatch is exactly
179
+ // what interception markers express). This only happens on a host render
180
+ // (children anchored at the origin path while the URL shows the target);
181
+ // when children == URL the server matched the real route and the slot must
182
+ // not intercept it. Interceptors go before direct matching: an interception
183
+ // route beats a catch-all inside the same slot.
184
+ if (context.soft && context.urlPath !== context.childrenPath) {
185
+ const intercepted = matchSlotInterceptor(context.appPath, slotDir, context.urlPath);
186
+ if (intercepted) {
187
+ context.stateOut[key] = context.urlPath + context.urlSearch;
188
+ return renderSlotMatch(context, slotDir, intercepted, context.urlSearch);
189
+ }
190
+ }
191
+
192
+ // Each candidate source carries the search string its render uses: a slot
193
+ // re-rendered from a RECORDED source keeps the query it was fetched with
194
+ // (Next's per-segment refetch URL), not the current request's.
195
+ const sources: { path: string; search: string }[] = [];
196
+ const pushSource = (path: string, search: string) => {
197
+ if (!sources.some(source => source.path === path)) sources.push({ path, search });
198
+ };
199
+ const rawStateSource = context.statePaths[key];
200
+ const stateSource = rawStateSource ? sourceParts(rawStateSource) : undefined;
201
+ const preserveForInterception =
202
+ context.soft &&
203
+ context.urlPath !== context.childrenPath &&
204
+ context.options.route.slotDirs?.some(dir =>
205
+ Boolean(matchSlotInterceptor(context.appPath, dir, context.urlPath)),
206
+ );
207
+ if (
208
+ preserveForInterception &&
209
+ stateSource &&
210
+ segmentsSharePrefix(pathnameSegments(stateSource.path), context.childrenSegments, consumed)
211
+ ) {
212
+ pushSource(stateSource.path, stateSource.search);
213
+ }
214
+ if (segmentsSharePrefix(context.urlSegments, context.childrenSegments, consumed)) {
215
+ pushSource(context.urlPath, context.urlSearch);
216
+ }
217
+ if (
218
+ context.soft &&
219
+ stateSource &&
220
+ segmentsSharePrefix(pathnameSegments(stateSource.path), context.childrenSegments, consumed)
221
+ ) {
222
+ pushSource(stateSource.path, stateSource.search);
223
+ }
224
+ // Host renders (children anchored away from the URL) fall back to the
225
+ // children path itself, so sibling slots keep matching the visible page even
226
+ // without an explicit state entry.
227
+ if (context.childrenPath !== context.urlPath) {
228
+ pushSource(context.childrenPath, context.childrenSearch);
229
+ }
230
+
231
+ for (const source of sources) {
232
+ const intercepted =
233
+ context.soft && source.path !== context.urlPath
234
+ ? matchSlotInterceptor(context.appPath, slotDir, source.path)
235
+ : null;
236
+ const match =
237
+ intercepted ?? matchSegments(slotDir, pathnameSegments(source.path).slice(consumed));
238
+ if (!match) continue;
239
+ context.stateOut[key] = source.path + source.search;
240
+ return renderSlotMatch(context, slotDir, match, source.search);
241
+ }
242
+
243
+ const defaultFile = slotConventionFile(slotDir, 'default');
244
+ if (!defaultFile) return null;
245
+ return renderSlotComponent(context, defaultFile, {});
246
+ }
247
+
248
+ /** Render a matched slot page inside its slot-internal layout chain. */
249
+ async function renderSlotMatch<Options extends SlotRenderOptions>(
250
+ context: SlotContext<Options>,
251
+ slotDir: string,
252
+ match: SegmentMatch,
253
+ sourceSearch?: string,
254
+ ): Promise<ComponentChildren> {
255
+ // A pageless match renders null children under the matched layout chain.
256
+ let tree: ComponentChildren = match.file
257
+ ? await renderSlotComponent(context, match.file, match.params, sourceSearch)
258
+ : null;
259
+
260
+ // Wrap innermost-out: loading.* at the page's own dir suspends the page;
261
+ // each dir's layout receives its own nested slots as props.
262
+ const chain: string[] = [slotDir];
263
+ for (const name of match.dirs) chain.push(join(chain[chain.length - 1]!, name));
264
+ context.matches.push({
265
+ slotDir,
266
+ ...(match.file ? { pageFile: match.file } : {}),
267
+ layoutFiles: chain.flatMap(dir => slotConventionFile(dir, 'layout') ?? []),
268
+ params: match.params,
269
+ });
270
+ for (const dir of [...chain].reverse()) {
271
+ const loadingFile = slotConventionFile(dir, 'loading');
272
+ if (loadingFile) {
273
+ const module = (await context.deps.importModule(
274
+ loadingFile,
275
+ context.options,
276
+ )) as LoadingModule;
277
+ const Loading = context.deps.markServerReference(module.default);
278
+ if (Loading) {
279
+ tree = h(
280
+ Suspense,
281
+ { fallback: h(Loading as ComponentType<Record<string, never>>, {}) },
282
+ tree,
283
+ );
284
+ }
285
+ }
286
+ const layoutFile = slotConventionFile(dir, 'layout');
287
+ if (!layoutFile) continue;
288
+ const module = (await context.deps.importModule(layoutFile, context.options)) as LayoutModule;
289
+ const Layout = context.deps.markServerReference(module.default);
290
+ if (!Layout) continue;
291
+ const nestedSlots = await renderDirSlots(context, dir);
292
+ tree = h(Layout as ComponentType<ServerVNodeProps>, {
293
+ ...slotPageProps(context, match.params, sourceSearch),
294
+ ...nestedSlots,
295
+ children: tree,
296
+ });
297
+ }
298
+ // Any `'use client'` island rendered anywhere inside this slot subtree must
299
+ // see the slot's own params (its dynamic / catch-all captures) from useParams,
300
+ // not just the global route params. Wrap the subtree in a params scope so the
301
+ // renderer stamps each island it resolves within it (see resolveServerTree's
302
+ // paramsScopeSymbol handling + render/island-context.ts RouteParamsContext).
303
+ return wrapSlotParamsScope(
304
+ { ...(context.options.params ?? {}), ...match.params },
305
+ tree,
306
+ );
307
+ }
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // Per-slot params scope (CORE seam for compat useParams inside a slot island).
311
+ //
312
+ // The scope is a marker VNode the server-tree resolver recognizes (via
313
+ // paramsScopeSymbol) and re-establishes as an async scope around resolving its
314
+ // children, so every island created deep inside the slot subtree captures the
315
+ // slot's resolved params. Mirrors the layout-segment scope above.
316
+ // ---------------------------------------------------------------------------
317
+
318
+ export const paramsScopeSymbol = Symbol.for('pnext.paramsScope');
319
+
320
+ const PARAMS_SCOPE_PROP = '__pnextParams';
321
+
322
+ const paramsSnapshotStorage = new AsyncLocalStorage<RouteParamsSnapshot>();
323
+
324
+ type ParamsScopeMarker = ((props: ServerVNodeProps) => ComponentChildren) & {
325
+ [paramsScopeSymbol]?: true;
326
+ };
327
+
328
+ const ParamsScopeMarker: ParamsScopeMarker = props => props.children;
329
+ ParamsScopeMarker[paramsScopeSymbol] = true;
330
+
331
+ // Parallel-route slot marker (task-boundary scoping). A named `@slot` subtree is wrapped in this marker so
332
+ // the server-tree resolver can arm the build prerender's task boundary ONLY while resolving slot content.
333
+ // That keeps the task-boundary abort - a slot that resolves in a task becomes a hole, matching Next - scoped
334
+ // to parallel-route slots, the shape it was designed for, and leaves every other <Suspense> baking its task
335
+ // work as before.
336
+
337
+ export const slotBoundarySymbol = Symbol.for('pnext.slotBoundary');
338
+
339
+ type SlotBoundaryMarker = ((props: ServerVNodeProps) => ComponentChildren) & {
340
+ [slotBoundarySymbol]?: true;
341
+ };
342
+
343
+ const SlotBoundaryMarker: SlotBoundaryMarker = props => props.children;
344
+ SlotBoundaryMarker[slotBoundarySymbol] = true;
345
+
346
+ /** Wrap a named parallel-route slot subtree in the task-boundary marker. */
347
+ export function wrapSlotBoundary(content: ComponentChildren): ComponentChildren {
348
+ return h(SlotBoundaryMarker as ComponentType<ServerVNodeProps>, null, content);
349
+ }
350
+
351
+ /** Read the params for the slot scope active in the current async resolution. */
352
+ export function currentParamsSnapshot(): RouteParamsSnapshot | undefined {
353
+ return paramsSnapshotStorage.getStore();
354
+ }
355
+
356
+ /** Run `fn` with the given slot params installed as the active params scope. */
357
+ export function runInParamsScope<T>(params: RouteParamsSnapshot, fn: () => T): T {
358
+ return paramsSnapshotStorage.run(params, fn);
359
+ }
360
+
361
+ /** Read the params a params-scope marker VNode carries. */
362
+ export function paramsScopeParams(props: Record<string, unknown>): RouteParamsSnapshot {
363
+ return (props[PARAMS_SCOPE_PROP] as RouteParamsSnapshot | undefined) ?? {};
364
+ }
365
+
366
+ function wrapSlotParamsScope(
367
+ params: Record<string, RouteParamValue>,
368
+ tree: ComponentChildren,
369
+ ): ComponentChildren {
370
+ return h(
371
+ ParamsScopeMarker as ComponentType<ServerVNodeProps>,
372
+ { [PARAMS_SCOPE_PROP]: params },
373
+ tree,
374
+ );
375
+ }
376
+
377
+ async function renderSlotComponent<Options extends SlotRenderOptions>(
378
+ context: SlotContext<Options>,
379
+ file: string,
380
+ params: Record<string, RouteParamValue>,
381
+ sourceSearch?: string,
382
+ ): Promise<ComponentChildren> {
383
+ const props = slotPageProps(context, params, sourceSearch);
384
+
385
+ // A 'use client' page inside a slot renders as an island: the reference
386
+ // stub becomes a hydrated boundary in resolveServerTree. Its promise props
387
+ // (params/searchParams) travel as plain markers the island runtime revives.
388
+ const reference = context.options.route.clientReferences.find(
389
+ item => item.file === file && item.exportName === 'default',
390
+ );
391
+ if (reference) {
392
+ const Component =
393
+ context.options.clientComponents?.get(reference.id) ??
394
+ context.deps.clientReferenceStub(reference);
395
+ return h(Component, {
396
+ params: promiseMarker(await props.params),
397
+ searchParams: promiseMarker(await props.searchParams),
398
+ });
399
+ }
400
+
401
+ const module = (await context.deps.importModule(file, context.options)) as PageModule;
402
+ const Component = context.deps.markServerReference(module.default);
403
+ if (!Component) return null;
404
+ return h(Component as ComponentType<PageProps>, props);
405
+ }
406
+
407
+ const PROMISE_MARKER_KEY = '__pnextPromise';
408
+
409
+ export function promiseMarker(value: unknown) {
410
+ return { [PROMISE_MARKER_KEY]: value ?? null };
411
+ }
412
+
413
+ /** The discriminator that makes a placeholder marker rewritable at serve time. */
414
+ const PROMISE_PLACEHOLDER_KEY = '__pnextParams';
415
+
416
+ /**
417
+ * The wire placeholder for a request-API promise prop that a partial prerender must NOT await (see ppr.ts
418
+ * `isHangingPromise`). The baked bytes carry no resolved param, and the discriminator names which request
419
+ * value re-stamps it per serving request - `data-pnext-props` is the third param carrier next to
420
+ * `window.__PNEXT_ROUTE__` and `data-pnext-params`, and all three must agree.
421
+ */
422
+ export function promisePlaceholderMarker(kind: 'params' | 'searchParams') {
423
+ return { [PROMISE_MARKER_KEY]: null, [PROMISE_PLACEHOLDER_KEY]: kind };
424
+ }
425
+
426
+ /** The serialized placeholder the serve-time re-stamp searches for. */
427
+ export function promisePlaceholderJson(kind: 'params' | 'searchParams'): string {
428
+ return `{"${PROMISE_MARKER_KEY}":null,"${PROMISE_PLACEHOLDER_KEY}":"${kind}"}`;
429
+ }
430
+
431
+ /** Revive promise markers into thenables for the SSR pass of an island. */
432
+ export function revivePromiseProps(props: Record<string, unknown>) {
433
+ const out: Record<string, unknown> = {};
434
+ for (const [key, value] of Object.entries(props)) {
435
+ if (value && typeof value === 'object' && PROMISE_MARKER_KEY in value) {
436
+ out[key] = Promise.resolve((value as Record<string, unknown>)[PROMISE_MARKER_KEY]);
437
+ } else {
438
+ out[key] = value;
439
+ }
440
+ }
441
+ return out;
442
+ }
443
+
444
+ function slotPageProps<Options extends SlotRenderOptions>(
445
+ context: SlotContext<Options>,
446
+ params: Record<string, RouteParamValue>,
447
+ sourceSearch?: string,
448
+ ): PageProps {
449
+ const base = context.deps.createPageProps(context.options);
450
+ // What createPageProps already resolves searchParams from: the children
451
+ // tree's recorded query on a host render, the request's otherwise (see
452
+ // childrenSourceSearchParams in renderer.ts).
453
+ const baseSearch =
454
+ context.soft && context.childrenPath !== trimPathname(context.options.url.pathname)
455
+ ? context.childrenSearch
456
+ : context.urlSearch;
457
+ return {
458
+ ...base,
459
+ params: Promise.resolve({ ...(context.options.params ?? {}), ...params }),
460
+ // A slot rendered from a source whose query differs (a recorded refetch
461
+ // URL, or the target URL's query on a host render) sees ITS source's
462
+ // searchParams — mirrors Next's per-segment refetch semantics.
463
+ ...(sourceSearch !== undefined && sourceSearch !== baseSearch
464
+ ? { searchParams: fulfilledPromise(searchParamsObject(sourceSearch)) }
465
+ : {}),
466
+ };
467
+ }
468
+
469
+ // Repeated keys collect into arrays (Next's searchParams page-prop shape).
470
+ function searchParamsObject(search: string): Record<string, string | string[]> {
471
+ const result: Record<string, string | string[]> = {};
472
+ new URLSearchParams(search).forEach((value, key) => {
473
+ if (key === '_rsc') return;
474
+ const current = result[key];
475
+ if (current === undefined) result[key] = value;
476
+ else if (Array.isArray(current)) current.push(value);
477
+ else result[key] = [current, value];
478
+ });
479
+ return result;
480
+ }
481
+
482
+ // Pre-settled promise (status/value readable synchronously) so preact's use()
483
+ // does not suspend during streaming SSR — mirrors renderer.ts settledPromise
484
+ // (not imported: renderer.ts imports from this module).
485
+ function fulfilledPromise<T>(value: T): Promise<T> {
486
+ const promise = Promise.resolve(value) as Promise<T> & { status?: string; value?: T };
487
+ promise.status = 'fulfilled';
488
+ promise.value = value;
489
+ return promise;
490
+ }
491
+
492
+ function segmentsSharePrefix(a: string[], b: string[], length: number) {
493
+ for (let index = 0; index < length; index += 1) {
494
+ if (a[index] !== b[index]) return false;
495
+ }
496
+ return true;
497
+ }
498
+
499
+ // ---------------------------------------------------------------------------
500
+ // Layout-segment context (CORE seam for compat useSelectedLayoutSegment(s)).
501
+ //
502
+ // Each layout in the chain is rendered inside a scope carrying the ordered list
503
+ // of URL segments BELOW that layout's own directory. `useSelectedLayoutSegment(s)`
504
+ // (compat/next/navigation.ts) reads the active scope. Route groups are kept as
505
+ // literal `(group)` entries; a catch-all is a single joined string; a page (leaf)
506
+ // component renders with an empty list. The scope is request-render-scoped so
507
+ // concurrent renders never leak segments across each other.
508
+ // ---------------------------------------------------------------------------
509
+
510
+ export interface LayoutSegmentScope {
511
+ segments: string[];
512
+ appPath?: string;
513
+ layoutDir?: string;
514
+ slots?: Record<string, string>;
515
+ }
516
+
517
+ const layoutSegmentStorage = new AsyncLocalStorage<LayoutSegmentScope>();
518
+
519
+ /**
520
+ * Run `fn` inside a layout-segment scope. The synchronous portion of a layout's
521
+ * body (where `useSelectedLayoutSegment(s)` is called, per hook rules) sees
522
+ * `segments`; nested layouts install their own deeper scope which correctly
523
+ * shadows this one across awaits (async_hooks propagation, like runWithRequest).
524
+ */
525
+ export function runInLayoutSegmentScope<T>(
526
+ segments: string[],
527
+ fn: () => T,
528
+ scope: Omit<LayoutSegmentScope, 'segments'> = {},
529
+ ): T {
530
+ return layoutSegmentStorage.run({ segments, ...scope }, fn);
531
+ }
532
+
533
+ /**
534
+ * Snapshot the active layout scope for stamping onto a client island (see
535
+ * render/island-context.ts). `depth` is how many segments the owning layout
536
+ * consumes; `segments` is its resolved "below me" list; `slots` maps each of
537
+ * the layout's OWN parallel-route slot keys to that slot's resolved segments.
538
+ * Returns undefined outside any layout scope (e.g. a leaf page island).
539
+ */
540
+ export function currentLayoutSegmentSnapshot():
541
+ | { depth: number; segments: string[]; slots: Record<string, string[]> }
542
+ | undefined {
543
+ const scope = layoutSegmentStorage.getStore();
544
+ if (!scope) return undefined;
545
+ const slots: Record<string, string[]> = {};
546
+ let depth = 0;
547
+ if (scope.appPath && scope.layoutDir) {
548
+ depth = layoutSegmentDepth(scope.appPath, scope.layoutDir);
549
+ const owner = toPosixPath(relative(scope.appPath, scope.layoutDir));
550
+ const prefix = owner ? `${owner}/@` : '@';
551
+ for (const [dir, path] of Object.entries(scope.slots ?? {})) {
552
+ if (!dir.startsWith(prefix)) continue;
553
+ const key = dir.slice(prefix.length);
554
+ if (key.includes('/')) continue;
555
+ slots[key] = pathnameSegments(path);
556
+ }
557
+ }
558
+ return { depth, segments: scope.segments, slots };
559
+ }
560
+
561
+ /**
562
+ * Whether a layout-segment scope is currently active. A server component in a
563
+ * layout runs inside one (currentLayoutSegments reads it); an island rendered to
564
+ * string does NOT (its scope closed during the resolve phase), so the compat
565
+ * hook falls back to the island context instead. Distinguishes the two callers.
566
+ */
567
+ export function hasLayoutSegmentScope(): boolean {
568
+ return layoutSegmentStorage.getStore() !== undefined;
569
+ }
570
+
571
+ /** The active layout's "segments below me", or `[]` outside any layout scope. */
572
+ export function currentLayoutSegments(slotKey?: string): string[] {
573
+ const scope = layoutSegmentStorage.getStore();
574
+ if (!scope) return [];
575
+ if (!slotKey) return scope.segments;
576
+ if (!scope.appPath || !scope.layoutDir) return [];
577
+ const owner = toPosixPath(relative(scope.appPath, scope.layoutDir));
578
+ const slotPath = scope.slots?.[owner ? `${owner}/@${slotKey}` : `@${slotKey}`];
579
+ return slotPath ? pathnameSegments(slotPath) : [];
580
+ }
581
+
582
+ /**
583
+ * The ordered URL segments from the app root down to the leaf, using the real
584
+ * on-disk directory chain (so route groups appear as literal `(group)` entries),
585
+ * with `[param]` dirs replaced by their decoded value, `[...catchall]` /
586
+ * `[[...catchall]]` dirs replaced by the joined catch-all string, and slots
587
+ * (`@name`) skipped. Empty optional catch-all contributes nothing.
588
+ */
589
+ export function fullLayoutSegments(
590
+ appPath: string,
591
+ leafDir: string,
592
+ params: Record<string, RouteParamValue>,
593
+ ): string[] {
594
+ const relativeDir = relative(appPath, leafDir);
595
+ if (relativeDir.startsWith('..')) return [];
596
+ const segments: string[] = [];
597
+ for (const name of relativeDir.split(sep).filter(Boolean)) {
598
+ if (isSlotSegment(name)) continue;
599
+ if (isGroupSegment(name)) {
600
+ segments.push(name);
601
+ continue;
602
+ }
603
+ const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(name);
604
+ const catchAll = optional ?? /^\[\.\.\.([^\]]+)\]$/.exec(name);
605
+ if (catchAll?.[1]) {
606
+ const value = params[catchAll[1]];
607
+ const joined = Array.isArray(value) ? value.join('/') : (value ?? '');
608
+ // An empty optional catch-all contributes no segment at all.
609
+ if (joined) segments.push(joined);
610
+ continue;
611
+ }
612
+ const dynamic = /^\[([^\].]+)\]$/.exec(name);
613
+ if (dynamic?.[1]) {
614
+ const value = params[dynamic[1]];
615
+ segments.push(Array.isArray(value) ? value.join('/') : String(value ?? ''));
616
+ continue;
617
+ }
618
+ segments.push(name);
619
+ }
620
+ return segments;
621
+ }
622
+
623
+ /**
624
+ * The params a layout at `layoutDir` receives: only those introduced by a
625
+ * dynamic segment AT or ABOVE the layout's own directory (Next scopes params
626
+ * per layout level). A catch-all param stays a JSON array; an EMPTY optional
627
+ * catch-all contributes no key at all (so the layout renders no params object).
628
+ * Params below the layout are excluded.
629
+ */
630
+ export function layoutParamsForDir(
631
+ appPath: string,
632
+ layoutDir: string,
633
+ params: Record<string, RouteParamValue>,
634
+ ): Record<string, RouteParamValue> {
635
+ const relativeDir = relative(appPath, layoutDir);
636
+ if (relativeDir.startsWith('..')) return {};
637
+ const scoped: Record<string, RouteParamValue> = {};
638
+ for (const name of relativeDir.split(sep).filter(Boolean)) {
639
+ if (isSlotSegment(name) || isGroupSegment(name)) continue;
640
+ const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(name);
641
+ if (optional?.[1]) {
642
+ const value = params[optional[1]];
643
+ // Empty optional catch-all: omit entirely (no params object for the layout).
644
+ if (value !== undefined && (Array.isArray(value) ? value.length > 0 : value != null)) {
645
+ scoped[optional[1]] = value;
646
+ }
647
+ continue;
648
+ }
649
+ const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(name);
650
+ if (catchAll?.[1]) {
651
+ const value = params[catchAll[1]];
652
+ if (value !== undefined) scoped[catchAll[1]] = value;
653
+ continue;
654
+ }
655
+ const dynamic = /^\[([^\].]+)\]$/.exec(name);
656
+ if (dynamic?.[1]) {
657
+ const value = params[dynamic[1]];
658
+ if (value !== undefined) scoped[dynamic[1]] = value;
659
+ }
660
+ }
661
+ return scoped;
662
+ }
663
+
664
+ /**
665
+ * Count how many entries of `fullLayoutSegments` a layout directory consumes
666
+ * (i.e. the segments AT or ABOVE the layout). The "segments below me" a layout
667
+ * receives is `full.slice(depthOfDir)`. Groups count as one entry (they are
668
+ * present in the full list); slots count as zero.
669
+ */
670
+ export function layoutSegmentDepth(appPath: string, layoutDir: string): number {
671
+ const relativeDir = relative(appPath, layoutDir);
672
+ if (relativeDir.startsWith('..') || relativeDir === '') return 0;
673
+ let depth = 0;
674
+ for (const name of relativeDir.split(sep).filter(Boolean)) {
675
+ if (isSlotSegment(name)) continue;
676
+ depth += 1;
677
+ }
678
+ return depth;
679
+ }