@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,1898 @@
1
+ // Core extension-point registry (CORE - imports ZERO compat).
2
+ //
3
+ // Core exposes a small set of mutable registries with working NO-OP defaults so a pure-core app (no
4
+ // next/* imports, no compat in pnext.config) runs unchanged. The compat layer populates them at the
5
+ // composition root through the single gated dynamic-import seam in compat-bootstrap.ts.
6
+ //
7
+ // The dependency edge is inverted: core NEVER imports compat. Instead compat imports core and
8
+ // registers its behavior here. Types stay intentionally structural (plain function shapes,
9
+ // `unknown` payloads) so core carries no type dependency on any compat module.
10
+
11
+ import type { OnLoadResult, Plugin } from 'esbuild';
12
+ import type { CompatAliasTarget, ResolvedConfig } from './config';
13
+ import type { StaticMetadataFile } from './routing/metadata';
14
+ import type {
15
+ BuildManifest,
16
+ ClientEntryReason,
17
+ RouteManifestEntry,
18
+ RouteParamValue,
19
+ } from './types';
20
+ import type { VerboseLogger } from './utils/verbose';
21
+ import { escapeRegex } from './utils/source';
22
+
23
+ /** Per-build wiring for the server plugin chain. */
24
+ export interface ServerEsbuildPluginOptions {
25
+ /**
26
+ * Vendor react-server pass only: stub a `'use client'` file as a client
27
+ * reference. Composed into the chain's single claiming onLoad, so the scan
28
+ * costs no extra plugin-callback round trips.
29
+ */
30
+ vendorClientBoundary?: (file: string) => Promise<OnLoadResult | undefined>;
31
+ /**
32
+ * The build's entries are realpath-resolved before esbuild sees them (the vendor chains), so
33
+ * workspace-link symlinks alone cannot put a symlinked importer in the graph - the symlink-import plugin
34
+ * may skip registration.
35
+ */
36
+ realPathEntries?: boolean;
37
+ }
38
+
39
+ // The extension host - ONE instance owning every domain registry below. A normal process runs on the
40
+ // module-level default host, and the getX()/setX()/register* surface reads it, so core consumers
41
+ // never see the host at all. Anything serving more than one app in one process (the test suite, an
42
+ // embedder) creates an isolated host and swaps it in, so registrations cannot leak between apps.
43
+
44
+ export interface ExtensionHost {
45
+ compatMode: CompatModeExtensions;
46
+ render: RenderExtensions;
47
+ request: RequestExtensions;
48
+ runtime: RuntimeExtensions;
49
+ build: BuildExtensions;
50
+ routing: RoutingExtensions;
51
+ proxy: ProxyExtensions;
52
+ proxyResponse: ProxyResponseProtocol;
53
+ routerProtocol: RouterProtocolExtensions;
54
+ bundler: BundlerExtensions;
55
+ importAlias: ImportAliasExtensions;
56
+ asset: AssetExtensions;
57
+ /** Import-only loadable extensions; page extensions live on `routing`. */
58
+ loadable: string[];
59
+ /** Memoized union of the server-transform chain's sniff tokens. */
60
+ serverSniff?: { pattern: RegExp | undefined };
61
+ }
62
+
63
+ export function createExtensionHost(): ExtensionHost {
64
+ return {
65
+ compatMode: createCompatModeExtensions(),
66
+ render: createRenderExtensions(),
67
+ request: createRequestExtensions(),
68
+ runtime: createRuntimeExtensions(),
69
+ build: createBuildExtensions(),
70
+ routing: createRoutingExtensions(),
71
+ proxy: createProxyExtensions(),
72
+ proxyResponse: createProxyResponseProtocol(),
73
+ routerProtocol: createRouterProtocolExtensions(),
74
+ bundler: createBundlerExtensions(),
75
+ importAlias: createImportAliasExtensions(),
76
+ asset: createAssetExtensions(),
77
+ loadable: [],
78
+ };
79
+ }
80
+
81
+ let activeHost = createExtensionHost();
82
+
83
+ /** The host every accessor in this module reads. */
84
+ export function getExtensionHost(): ExtensionHost {
85
+ return activeHost;
86
+ }
87
+
88
+ // Registration guards that live OUTSIDE any host — compat-bootstrap's promises,
89
+ // each register-* module's `registered` flag — must clear when the host is
90
+ // swapped, or the fresh host is left permanently empty. Module-global on
91
+ // purpose: the guards they clear are module-global too.
92
+ const resetHandlers: (() => void)[] = [];
93
+
94
+ // The detectors are regex sweeps over whole sources, and a route's layout chain is re-detected for
95
+ // every route below it. The answer depends only on the source and the registered detector set, so
96
+ // memoize on exactly that. Cleared whenever a detector registers.
97
+ const clientEntryReasonCache = new Map<string, ClientEntryReason[]>();
98
+
99
+ /** Run `handler` whenever the active host is swapped. */
100
+ export function onExtensionHostReset(handler: () => void): void {
101
+ resetHandlers.push(handler);
102
+ }
103
+
104
+ /**
105
+ * Tie a registry that lives outside the host (render/hooks, the CSS extras below) to the host's
106
+ * lifetime: compat populates it exactly like the host's own registries, so it must return to its
107
+ * core defaults with them. Restored in place rather than replaced - consumers may hold the object.
108
+ */
109
+ export function restoreWithExtensionHost<T extends object>(registry: T): void {
110
+ const defaults = { ...registry };
111
+ onExtensionHostReset(() => {
112
+ for (const key of Object.keys(registry)) delete (registry as Record<string, unknown>)[key];
113
+ Object.assign(registry, defaults);
114
+ });
115
+ }
116
+
117
+ /** Install `host` as the active one; the returned function restores the previous. */
118
+ export function setActiveHost(host: ExtensionHost): () => void {
119
+ const previous = activeHost;
120
+ activeHost = host;
121
+ announceHostChange();
122
+ return () => {
123
+ activeHost = previous;
124
+ announceHostChange();
125
+ };
126
+ }
127
+
128
+ /** Swap in a fresh host, dropping every registration on the current one. */
129
+ export function resetExtensionHost(): ExtensionHost {
130
+ const host = createExtensionHost();
131
+ setActiveHost(host);
132
+ return host;
133
+ }
134
+
135
+ /** Run `callback` against `host`, restoring the previous host afterwards. */
136
+ export function withExtensionHost<T>(host: ExtensionHost, callback: () => T): T {
137
+ const restore = setActiveHost(host);
138
+ try {
139
+ const result = callback();
140
+ if (isThenable(result)) return Promise.resolve(result).finally(restore) as T;
141
+ restore();
142
+ return result;
143
+ } catch (error) {
144
+ restore();
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ function announceHostChange() {
150
+ for (const handler of resetHandlers) handler();
151
+ // Consumers latch the loadable-extension set (the server load plugin's filter
152
+ // regex); a swap changes it, so invalidate exactly as a registration would.
153
+ for (const listener of loadableExtensionListeners) listener();
154
+ // Memoized detector answers belong to the host that registered the detectors.
155
+ clientEntryReasonCache.clear();
156
+ }
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // Compat mode extensions — populated by compat/index.
160
+ // ---------------------------------------------------------------------------
161
+
162
+ export interface ReactCompilerOptions {
163
+ target: string;
164
+ }
165
+
166
+ export interface CompatModeExtensions {
167
+ nextEnabled: (config: ResolvedConfig) => boolean;
168
+ reactEnabled: (config: ResolvedConfig) => boolean;
169
+ reactCompilerOptions: (config: ResolvedConfig) => ReactCompilerOptions | undefined;
170
+ }
171
+
172
+ function createCompatModeExtensions(): CompatModeExtensions {
173
+ return {
174
+ nextEnabled: () => false,
175
+ reactEnabled: () => false,
176
+ reactCompilerOptions: () => undefined,
177
+ };
178
+ }
179
+
180
+ export function getCompatModeExtensions(): CompatModeExtensions {
181
+ return activeHost.compatMode;
182
+ }
183
+
184
+ export function setCompatModeExtensions(overrides: Partial<CompatModeExtensions>): void {
185
+ Object.assign(activeHost.compatMode, overrides);
186
+ }
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Render extensions — populated by compat/react + compat/actions + compat/cache
190
+ // (registerRenderExtensions).
191
+ // ---------------------------------------------------------------------------
192
+
193
+ /** Result of collecting cache metadata around a render (matches compat/cache/revalidate collectRenderCacheMeta). */
194
+ export interface RenderCacheMetaResult<T> {
195
+ value: T;
196
+ tags: string[];
197
+ revalidateSeconds?: number;
198
+ /** Aggregated `use cache` cacheLife expire/stale windows (min across scopes). */
199
+ expireSeconds?: number;
200
+ staleSeconds?: number;
201
+ noStore?: boolean;
202
+ }
203
+
204
+ /** Options threaded into a cache-meta collection scope. */
205
+ export interface CollectRenderMetaOptions {
206
+ fetchCache?: string;
207
+ refreshFetches?: boolean;
208
+ blockingStaleFetches?: boolean;
209
+ route?: string;
210
+ /** True for build-time static generation renders (prerender fetch caching). */
211
+ prerender?: boolean;
212
+ /** True when the render is a route handler (vs a page/layout render). */
213
+ handler?: boolean;
214
+ /** True when a static render must reject dynamic request APIs. */
215
+ dynamicError?: boolean;
216
+ }
217
+
218
+ export interface RenderExtensions {
219
+ /** Enrich the client route state embedded in a rendered document. */
220
+ clientRouteState: (
221
+ route: Pick<
222
+ RouteManifestEntry,
223
+ | 'route'
224
+ | 'file'
225
+ | 'hasStaticParams'
226
+ | 'usesRequest'
227
+ | 'segmentConfig'
228
+ | 'prerenderedParams'
229
+ | 'params'
230
+ | 'stream'
231
+ >,
232
+ state: {
233
+ route: string;
234
+ params: Record<string, RouteParamValue>;
235
+ catchAllOptional?: boolean;
236
+ pageVary?: string[];
237
+ },
238
+ ) => {
239
+ route: string;
240
+ params: Record<string, RouteParamValue>;
241
+ catchAllOptional?: boolean;
242
+ /** Baked-shell vary set (see renderer `bakedShellPageVary`). */
243
+ pageVary?: string[];
244
+ staticChildren?: string[];
245
+ staticChildrenBySegment?: Record<string, string[]>;
246
+ prefetchKind?: 'shell' | 'eager';
247
+ /** `prefetch = 'allow-runtime'` — the shell is per-URL, not route-shared. */
248
+ runtimePrefetch?: boolean;
249
+ };
250
+ /**
251
+ * Wrap a single server-component invocation. Compat (react) installs the
252
+ * use()-thenable replay wrapper here (compat/react/preact withUseThenableState
253
+ * + render/index.tsx invokeServerComponentWithUse). Default: call through untouched.
254
+ */
255
+ wrapServerComponentInvoke: <T>(invoke: () => T) => T;
256
+
257
+ /**
258
+ * Optional one-time render-module initialization side effect. Default: noop.
259
+ */
260
+ onRenderInit: () => void;
261
+
262
+ /**
263
+ * Convert a value crossing the server->client boundary into a serializable
264
+ * action reference, or return undefined to leave it untouched. Compat
265
+ * (actions) returns the `{ [PROP_ACTION_MARKER]: id }` marker for tagged /
266
+ * inline / bound server-action functions (compat/actions server-tag +
267
+ * protocol + instances + client-plugin). Default: undefined (no rewrite).
268
+ *
269
+ * `context` carries the active render's config + a stable per-render key so
270
+ * compat can scope inline-action instance registration; core passes its
271
+ * RenderOptions object as the key (opaque to core).
272
+ */
273
+ serializeServerActionProp: (
274
+ value: unknown,
275
+ context: ServerActionPropContext,
276
+ ) => ActionRef | undefined;
277
+
278
+ /**
279
+ * Run `produce` inside a cache-meta collection scope, returning its value plus
280
+ * the tags/revalidate/no-store it recorded. Compat (cache) supplies the real
281
+ * AsyncLocalStorage collector (compat/cache/revalidate collectRenderCacheMeta).
282
+ * Default: run `produce`, report no tags.
283
+ */
284
+ collectRenderMeta: <T>(
285
+ produce: () => Promise<T>,
286
+ options?: CollectRenderMetaOptions,
287
+ ) => Promise<RenderCacheMetaResult<T>>;
288
+
289
+ /**
290
+ * The effective `use cache` stale window (seconds) recorded SO FAR by the surrounding
291
+ * `collectRenderMeta` scope, without consuming it. The renderer inlines this into a document's
292
+ * `__PNEXT_NAV_STATE__` so a HARD LOAD - which has no `x-nextjs-stale-time` header to read - seeds
293
+ * its caches with the route's real reuse window. The route manifest's `cacheLife` cannot serve
294
+ * here: it is captured FROM this very render and persisted only after the HTML exists.
295
+ */
296
+ currentCacheStaleSeconds: () => number | undefined;
297
+
298
+ /**
299
+ * Override the generic error shell title. Compat uses this for Next's
300
+ * production client-error page copy; core keeps the compact default.
301
+ */
302
+ genericErrorTitle: (context: GenericErrorTitleContext) => string | undefined;
303
+
304
+ /**
305
+ * Whether islands travel the wire as comment markers instead of elements. Core's wire format is
306
+ * elements-as-final: `<pnext-client>` hosts and the page slot ship as real elements the entry
307
+ * hydrates in place, so a page with no islands ships no inline JavaScript at all. Next asserts
308
+ * structural selectors against the live DOM (`body > p`), which a wrapper element would break, so
309
+ * compat turns the elements into comment pairs plus a bootstrap that materializes them back.
310
+ */
311
+ islandCommentWireFormat: () => boolean;
312
+
313
+ /**
314
+ * Serialize a caught render error into the `{ name, message, digest }` shape
315
+ * handed to error.js / global-error.js boundaries. Compat implements Next's
316
+ * digest protocol: hash server errors into a stable digest, pass through a
317
+ * user-supplied `.digest`, redact the message in production (React #441 text),
318
+ * and log the original stack once to the server console. Default: return
319
+ * undefined so core keeps its own compact serialization.
320
+ */
321
+ serializeError: (context: SerializeErrorContext) => SerializedError | undefined;
322
+
323
+ /**
324
+ * Next-pixel-exact default UI for an HTTP-access fallback (not-found 404,
325
+ * forbidden 403, unauthorized 401) when the app ships no boundary file.
326
+ * Returns a render tree (preact VNode, opaque to core as `unknown`) matching
327
+ * Next's HTTPAccessErrorFallback (`h1.next-error-h1` + `h2` message). Core
328
+ * keeps a compact built-in when this is absent. `status` is the HTTP status,
329
+ * `message` the copy shown in the `h2`.
330
+ */
331
+ httpAccessFallbackUi: (status: number, message: string) => unknown;
332
+
333
+ /**
334
+ * Next-pixel-exact built-in global-error document ("This page couldn't load"
335
+ * + digest footer) rendered as a whole HTML document when an error escapes the
336
+ * root layout and the app ships no global-error.*. Returns a render tree
337
+ * (preact VNode, opaque to core) or undefined to keep core's compact fallback.
338
+ */
339
+ defaultGlobalErrorUi: (error: SerializedError) => unknown;
340
+
341
+ /**
342
+ * Inline bootstrap markup appended to a global-error document (built-in
343
+ * fallback or a user global-error.*) so the client observes the escaped
344
+ * error as an uncaught exception, matching Next's hydration-replay signal
345
+ * (Playwright `pageerror`). Returns an HTML string (typically a `<script>`
346
+ * tag) or undefined to render the document with no bootstrap (core default).
347
+ */
348
+ globalErrorReportScript: (error: SerializedError) => string | undefined;
349
+
350
+ /**
351
+ * Wrap a non-Error thrown value (a `throw undefined`/`null`/'msg' from a
352
+ * server component) into the Error the error pipeline logs and serializes.
353
+ * Compat mirrors Next's RSC transport wrapper: message String(value), a
354
+ * synthetic `at stringify (<anonymous>)` stack, and a raw-value tag so the
355
+ * digest gains Next's `@E394` suffix for nullish throws. Default: undefined
356
+ * (core wraps with a plain `new Error(String(value))`).
357
+ */
358
+ wrapThrownValue: (value: unknown) => Error | undefined;
359
+
360
+ /**
361
+ * Format the server-console log line for a render error that carries a boundary digest. Compat
362
+ * emits Next's exact prod shape, plus any environment tag the error carries (an error funneled out
363
+ * of a 'use cache' scope inspects as `{ environmentName: 'Cache', digest }`).
364
+ */
365
+ formatErrorLog: (trace: string, digest: string, error?: Error) => string | undefined;
366
+
367
+ /**
368
+ * True when the app opted into the global-not-found convention: an unmatched URL then renders the
369
+ * global-not-found document, and with no global-not-found.* file it falls back to the BUILT-IN
370
+ * default 404, never the app's root not-found.* boundary (reserved for explicit notFound() calls).
371
+ */
372
+ globalNotFoundEnabled: () => boolean;
373
+
374
+ /**
375
+ * Wrap a render's concrete `params` / `searchParams` object in a tracking view so compat can record
376
+ * which of them the render actually reads. Core never interprets the result - it just hands the
377
+ * returned object to user code in place of the original. Default: identity.
378
+ */
379
+ trackVaryParams: <T extends Record<string, unknown>>(
380
+ value: T,
381
+ context: VaryParamsTrackingContext,
382
+ ) => T;
383
+
384
+ /** Optional opaque state lifecycle around partial prerenders. */
385
+ prerenderSidecar: PrerenderSidecarExtension;
386
+ }
387
+
388
+ /** What kind of object `trackVaryParams` is being handed, plus route shape. */
389
+ export interface VaryParamsTrackingContext {
390
+ kind: 'params' | 'searchParams';
391
+ /**
392
+ * Which route segment is about to read this object. Compat keys its
393
+ * per-segment vary sets on it, so a layout that reads more params than the
394
+ * page does not force the page's cache entry to vary on them too. Defaults
395
+ * to the leaf page.
396
+ */
397
+ segment?: 'page' | 'layout';
398
+ /**
399
+ * Name of an OPTIONAL catch-all param of this route (`[[...slug]]`), when
400
+ * any. Its key is absent from `params` at the empty value, so tracking it
401
+ * needs the name up front.
402
+ */
403
+ optionalCatchAllParam?: string;
404
+ }
405
+
406
+ export interface GenericErrorTitleContext {
407
+ error: Error;
408
+ dev: boolean;
409
+ }
410
+
411
+ /** Context handed to the error-serialization extension. */
412
+ export interface SerializeErrorContext {
413
+ error: Error;
414
+ dev: boolean;
415
+ }
416
+
417
+ /** The `{ name, message, digest }` object passed to an error boundary component. */
418
+ export interface SerializedError {
419
+ name: string;
420
+ message: string;
421
+ digest?: string;
422
+ }
423
+
424
+ /** Opaque scope handed to serializeServerActionProp (config + per-render key). */
425
+ export interface ServerActionPropContext {
426
+ config: ResolvedConfig;
427
+ /** Stable per-render identity; core passes its RenderOptions object. */
428
+ renderKey: object;
429
+ /**
430
+ * Only answer for a function that is IDENTIFIABLY a server action (a tagged
431
+ * module export, a compile-tagged inline `'use server'` closure, an
432
+ * explicitly overridden id). An unidentifiable function must return
433
+ * undefined instead of being registered as a live per-render instance.
434
+ *
435
+ * Used where the call site cannot distinguish a server action from an
436
+ * ordinary client closure: `<button onClick={fn}>` inside a `'use client'`
437
+ * subtree is always a client handler, and registering it would both stamp
438
+ * the element with a bogus action wire and shift the occurrence indices real
439
+ * inline actions are keyed on.
440
+ */
441
+ identifiedOnly?: boolean;
442
+ }
443
+
444
+ /** Serializable marker a compat client runtime revives back into an action. */
445
+ export type ActionRef = Record<string, unknown>;
446
+
447
+ export interface PrerenderSidecarContext {
448
+ outPath: string;
449
+ routeId: string;
450
+ routePath: string;
451
+ }
452
+
453
+ export interface PrerenderSidecarExtension {
454
+ begin: (context: PrerenderSidecarContext) => void | Promise<void>;
455
+ collect: (context: PrerenderSidecarContext) => Promise<unknown>;
456
+ persist: (context: PrerenderSidecarContext, value: unknown) => Promise<void>;
457
+ seed: (context: PrerenderSidecarContext) => void | Promise<void>;
458
+ /**
459
+ * Whether the persisted sidecar/shell went stale. `true` = hard staleness
460
+ * (block and regenerate before responding); `'soft'` = stale-while-
461
+ * revalidate (serve the stale shell once, regenerate in the background).
462
+ */
463
+ isStale: (context: PrerenderSidecarContext) => boolean | 'soft' | Promise<boolean | 'soft'>;
464
+ }
465
+
466
+ function createRenderExtensions(): RenderExtensions {
467
+ return {
468
+ clientRouteState: (_route, state) => state,
469
+ wrapServerComponentInvoke: invoke => invoke(),
470
+ onRenderInit: () => undefined,
471
+ serializeServerActionProp: () => undefined,
472
+ collectRenderMeta: async (produce, _options) => ({ value: await produce(), tags: [] }),
473
+ currentCacheStaleSeconds: () => undefined,
474
+ genericErrorTitle: () => undefined,
475
+ islandCommentWireFormat: () => false,
476
+ serializeError: () => undefined,
477
+ httpAccessFallbackUi: () => undefined,
478
+ defaultGlobalErrorUi: () => undefined,
479
+ globalErrorReportScript: () => undefined,
480
+ wrapThrownValue: () => undefined,
481
+ formatErrorLog: () => undefined,
482
+ globalNotFoundEnabled: () => false,
483
+ trackVaryParams: value => value,
484
+ prerenderSidecar: {
485
+ begin: () => undefined,
486
+ collect: () => Promise.resolve(undefined),
487
+ persist: () => Promise.resolve(),
488
+ seed: () => undefined,
489
+ isStale: () => false,
490
+ },
491
+ };
492
+ }
493
+
494
+ export function getRenderExtensions(): RenderExtensions {
495
+ return activeHost.render;
496
+ }
497
+
498
+ export function setRenderExtensions(overrides: Partial<RenderExtensions>): void {
499
+ Object.assign(activeHost.render, overrides);
500
+ }
501
+
502
+ // ---------------------------------------------------------------------------
503
+ // Request extensions — populated by compat/actions + compat/next/rewrites +
504
+ // compat/cache (registerActionExtensions).
505
+ // ---------------------------------------------------------------------------
506
+
507
+ /**
508
+ * A request interceptor runs before route matching. Return a `Response` to
509
+ * short-circuit, `{ request }` to swap the request (e.g. a rewrite) and
510
+ * continue, or undefined to pass through unchanged. Ordered; core runs them in
511
+ * registration order.
512
+ */
513
+ export type RequestInterceptor = (
514
+ request: Request,
515
+ ctx: RequestInterceptorContext,
516
+ ) => Promise<Response | { request: Request } | undefined>;
517
+
518
+ /** Context handed to each request interceptor. */
519
+ export interface RequestInterceptorContext {
520
+ config: ResolvedConfig;
521
+ /**
522
+ * The request's path falls OUTSIDE the configured basePath, so core would
523
+ * otherwise 404 it. Only rules that opt out of the basePath (next.config's
524
+ * `basePath: false` rewrites/redirects) may answer such a request; every other
525
+ * interceptor must decline. Absent (falsy) on every in-app request.
526
+ */
527
+ outsideBasePath?: boolean;
528
+ }
529
+
530
+ // Response finalizers - ordered fns run before the first flush of every response-producing call site
531
+ // (page renders, route handlers, static file serving, redirects, 404s). Finalizers observe the
532
+ // outgoing status/headers plus request info and may mutate them in place. Core registers none;
533
+ // compat adds RSC Vary values, x-nextjs-* headers and exact ISR cache-control strings here.
534
+
535
+ /** How the matched route was served — shapes the finalizer decisions. */
536
+ export type ResponseRouteKind = 'html' | 'data' | 'static-asset' | 'route-handler';
537
+
538
+ /** The route's caching disposition when known (page/handler renders). */
539
+ export type ResponseRouteMode = 'static' | 'isr' | 'dynamic';
540
+
541
+ /** Read-only request facts a finalizer keys its header/status decisions on. */
542
+ export interface ResponseFinalizerRequest {
543
+ method: string;
544
+ url: URL;
545
+ headers: Headers;
546
+ }
547
+
548
+ /**
549
+ * Mutable finalizer context handed to each finalizer before the first flush.
550
+ * `status` and `headers` are the outgoing response's own status/headers; a
551
+ * finalizer mutates them in place (set `status` to override, append/merge on
552
+ * `headers`). Classification fields are optional so compat can grow (e.g. RSC
553
+ * kind, rewrite tracking) with no core change; ad-hoc compat hints ride on
554
+ * `hints`.
555
+ */
556
+ export interface ResponseFinalizerContext {
557
+ request: ResponseFinalizerRequest;
558
+ /** How the response was produced. */
559
+ routeKind: ResponseRouteKind;
560
+ /** Caching disposition of the matched route, when core knows it. */
561
+ routeMode?: ResponseRouteMode;
562
+ /** Outgoing status; assign to override before first flush. */
563
+ status: number;
564
+ /** Outgoing headers; mutate in place (merge Vary, set Cache-Control, ...). */
565
+ headers: Headers;
566
+ /** Compat-only classification hints (RSC kind, rewrite target, ...). */
567
+ hints?: Record<string, unknown>;
568
+ }
569
+
570
+ /** A response finalizer runs before first flush; mutations land on the response. */
571
+ export type ResponseFinalizer = (ctx: ResponseFinalizerContext) => void | Promise<void>;
572
+
573
+ /** Request facts handed to the error funnel (Next's onRequestError request arg). */
574
+ export interface RequestErrorInfo {
575
+ method: string;
576
+ url: string;
577
+ headers: Headers;
578
+ }
579
+
580
+ /** Work-unit facts handed to the error funnel (phase, matched route kind). */
581
+ export interface RequestErrorContext {
582
+ /** The active work-unit phase when the error was caught, if any. */
583
+ phase?: string;
584
+ /** How the failing request was being served. */
585
+ routeKind?: ResponseRouteKind;
586
+ /** Optional compat-neutral details forwarded by the active work unit. */
587
+ renderSource?: string;
588
+ revalidateReason?: string;
589
+ }
590
+
591
+ export interface RequestExtensions {
592
+ /** Ordered interceptors run before route matching (action dispatch, rewrites). */
593
+ interceptors: RequestInterceptor[];
594
+
595
+ /**
596
+ * Fixed first-request costs an extension would otherwise pay inside the first
597
+ * interceptor (compat registers the `@opentelemetry/api` resolve). Dev runs
598
+ * them in the background after Ready; they must be idempotent, cheap to skip,
599
+ * and never route-specific.
600
+ */
601
+ warmHooks: ((config: ResolvedConfig) => void)[];
602
+
603
+ /**
604
+ * Interceptors for a request whose path lies OUTSIDE the configured basePath - one core 404s before
605
+ * any routing. Only rules that explicitly opt out of the basePath belong here; everything else must
606
+ * not see such a request. Never consulted without a basePath.
607
+ */
608
+ outsideBasePathInterceptors: RequestInterceptor[];
609
+
610
+ /**
611
+ * Ordered response finalizers run before the first flush on every
612
+ * response-producing call site. Each observes request info + the outgoing
613
+ * status/headers and may mutate status/headers in place. Compat appends RSC
614
+ * Vary values, x-nextjs-* headers, and exact ISR cache-control strings.
615
+ */
616
+ responseFinalizers: ResponseFinalizer[];
617
+
618
+ /**
619
+ * The request-level error funnel. Core calls this exactly once from the single place it catches a
620
+ * request error, with the error, request info and work-unit context. Compat classifies control-flow
621
+ * errors vs real errors and implements onRequestError. Core does NOT swallow the error - it still
622
+ * surfaces its own 500.
623
+ */
624
+ onRequestError: (
625
+ error: unknown,
626
+ requestInfo: RequestErrorInfo,
627
+ context: RequestErrorContext,
628
+ ) => void | Promise<void>;
629
+
630
+ /**
631
+ * Whether a prebuilt static file for `pathname` (written at `mtimeMs`, with the given cache `tags`)
632
+ * has been invalidated since it was built - the ISR on-demand staleness check. Default: never stale.
633
+ */
634
+ staticStaleness: (pathname: string, mtimeMs: number, tags: readonly string[]) => boolean;
635
+ /** Why the same static file is stale, when available. */
636
+ staticStalenessReason: (
637
+ pathname: string,
638
+ mtimeMs: number,
639
+ tags: readonly string[],
640
+ ) => 'stale' | 'on-demand' | 'soft' | undefined;
641
+
642
+ /** Trigger immediate ISR regeneration for pages API res.revalidate(). */
643
+ onDemandRevalidatePath: (pathname: string) => void | Promise<void>;
644
+
645
+ /**
646
+ * The preview-mode id an `x-prerender-revalidate` request header must match
647
+ * for Next's on-demand revalidation semantics: middleware is skipped and the
648
+ * route re-renders fresh (`x-nextjs-cache: REVALIDATED`). Compat registers
649
+ * the id it writes into prerender-manifest.json. Default: none (no bypass).
650
+ */
651
+ revalidateBypassToken: () => string | undefined;
652
+ }
653
+
654
+ function createRequestExtensions(): RequestExtensions {
655
+ return {
656
+ interceptors: [],
657
+ warmHooks: [],
658
+ outsideBasePathInterceptors: [],
659
+ responseFinalizers: [],
660
+ onRequestError: () => undefined,
661
+ staticStaleness: () => false,
662
+ staticStalenessReason: () => undefined,
663
+ onDemandRevalidatePath: () => undefined,
664
+ revalidateBypassToken: () => undefined,
665
+ };
666
+ }
667
+
668
+ export function getRequestExtensions(): RequestExtensions {
669
+ return activeHost.request;
670
+ }
671
+
672
+ /** Append request interceptors in order (compat registers action dispatch + rewrites). */
673
+ export function registerRequestInterceptors(...interceptors: RequestInterceptor[]): void {
674
+ activeHost.request.interceptors.push(...interceptors);
675
+ }
676
+
677
+ /** Register a fixed first-request cost the dev warm can pay in the background. */
678
+ export function registerRequestWarmHooks(...hooks: ((config: ResolvedConfig) => void)[]): void {
679
+ activeHost.request.warmHooks.push(...hooks);
680
+ }
681
+
682
+ /** Pay every registered first-request cost up front. Failures are never fatal. */
683
+ export function runRequestWarmHooks(config: ResolvedConfig): void {
684
+ for (const hook of activeHost.request.warmHooks) {
685
+ try {
686
+ hook(config);
687
+ } catch {
688
+ // A warm that fails just leaves the cost on the first request.
689
+ }
690
+ }
691
+ }
692
+
693
+ /** Append interceptors that may answer a request from outside the basePath. */
694
+ export function registerOutsideBasePathInterceptors(...interceptors: RequestInterceptor[]): void {
695
+ activeHost.request.outsideBasePathInterceptors.push(...interceptors);
696
+ }
697
+
698
+ /** Append response finalizers in order (compat registers RSC headers + ISR cache-control). */
699
+ export function registerResponseFinalizers(...finalizers: ResponseFinalizer[]): void {
700
+ activeHost.request.responseFinalizers.push(...finalizers);
701
+ }
702
+
703
+ /** Register the preview-mode id that authorizes on-demand revalidate requests. */
704
+ export function registerRevalidateBypassToken(token: () => string | undefined): void {
705
+ activeHost.request.revalidateBypassToken = token;
706
+ }
707
+
708
+ /**
709
+ * Run every registered response finalizer against `response` before it is
710
+ * flushed, then return the response to send. Finalizers see request info + the
711
+ * outgoing status/headers and mutate them in place; when a finalizer changes the
712
+ * status (immutable on a Response) the body is re-wrapped with the new status. A
713
+ * no-finalizer app (pure core) returns the response untouched.
714
+ */
715
+ export async function finalizeResponse(
716
+ response: Response,
717
+ request: ResponseFinalizerRequest,
718
+ info: {
719
+ routeKind: ResponseRouteKind;
720
+ routeMode?: ResponseRouteMode;
721
+ hints?: Record<string, unknown>;
722
+ },
723
+ ): Promise<Response> {
724
+ const finalizers = activeHost.request.responseFinalizers;
725
+ if (finalizers.length === 0) return response;
726
+ const ctx: ResponseFinalizerContext = {
727
+ request,
728
+ routeKind: info.routeKind,
729
+ ...(info.routeMode ? { routeMode: info.routeMode } : {}),
730
+ ...(info.hints ? { hints: info.hints } : {}),
731
+ status: response.status,
732
+ headers: response.headers,
733
+ };
734
+ for (const finalizer of finalizers) await finalizer(ctx);
735
+ if (ctx.status === response.status) return response;
736
+ return new Response(response.body, {
737
+ status: ctx.status,
738
+ statusText: response.statusText,
739
+ headers: response.headers,
740
+ });
741
+ }
742
+
743
+ export function setRequestExtensions(
744
+ overrides: Partial<Omit<RequestExtensions, 'interceptors' | 'responseFinalizers'>>,
745
+ ): void {
746
+ Object.assign(activeHost.request, overrides);
747
+ }
748
+
749
+ /**
750
+ * Report a request-level error through the compat error funnel. Core calls this from the single place
751
+ * it catches request errors. Never throws - reporting failures are swallowed so the caller's own
752
+ * error handling proceeds.
753
+ */
754
+ export async function reportRequestError(
755
+ error: unknown,
756
+ requestInfo: RequestErrorInfo,
757
+ context: RequestErrorContext,
758
+ ): Promise<void> {
759
+ try {
760
+ await activeHost.request.onRequestError(error, requestInfo, context);
761
+ } catch (reportError) {
762
+ console.error('onRequestError funnel failed:', reportError);
763
+ }
764
+ }
765
+
766
+ // ---------------------------------------------------------------------------
767
+ // Runtime extensions — populated by compat/edge-runtime.
768
+ // ---------------------------------------------------------------------------
769
+
770
+ export interface RuntimeExtensions {
771
+ /** Run user code with Edge-runtime globals installed. Default: no-op. */
772
+ withEdgeRuntime: <T>(callback: () => T) => T;
773
+ }
774
+
775
+ function createRuntimeExtensions(): RuntimeExtensions {
776
+ return { withEdgeRuntime: callback => callback() };
777
+ }
778
+
779
+ export function getRuntimeExtensions(): RuntimeExtensions {
780
+ return activeHost.runtime;
781
+ }
782
+
783
+ export function setRuntimeExtensions(overrides: Partial<RuntimeExtensions>): void {
784
+ Object.assign(activeHost.runtime, overrides);
785
+ }
786
+
787
+ export function withRouteRuntime<T>(runtime: string | undefined, callback: () => T): T {
788
+ const edge = runtime === 'edge' || runtime === 'experimental-edge';
789
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
790
+ const previous = process.env.NEXT_RUNTIME;
791
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
792
+ process.env.NEXT_RUNTIME = edge ? 'edge' : 'nodejs';
793
+ try {
794
+ const result = edge ? activeHost.runtime.withEdgeRuntime(callback) : callback();
795
+ if (isThenable(result)) {
796
+ return Promise.resolve(result).finally(() => restoreNextRuntime(previous)) as T;
797
+ }
798
+ restoreNextRuntime(previous);
799
+ return result;
800
+ } catch (error) {
801
+ restoreNextRuntime(previous);
802
+ throw error;
803
+ }
804
+ }
805
+
806
+ function restoreNextRuntime(value: string | undefined) {
807
+ if (value === undefined) {
808
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
809
+ delete process.env.NEXT_RUNTIME;
810
+ } else {
811
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
812
+ process.env.NEXT_RUNTIME = value;
813
+ }
814
+ }
815
+
816
+ function isThenable<T>(value: T): value is T & PromiseLike<unknown> {
817
+ return typeof (value as { then?: unknown } | null | undefined)?.then === 'function';
818
+ }
819
+
820
+ // ---------------------------------------------------------------------------
821
+ // Build extensions — populated by compat/actions + compat/cache/fetch-patch
822
+ // (registerActionExtensions + registerCacheExtensions).
823
+ // ---------------------------------------------------------------------------
824
+
825
+ /** Minimal build context handed to each build step (kept structural on purpose). */
826
+ export interface BuildStepContext {
827
+ config: ResolvedConfig;
828
+ routes: unknown[];
829
+ manifest: unknown;
830
+ log: BuildStepLogger;
831
+ }
832
+
833
+ /**
834
+ * The verbose build logger's step/log surface handed to each build step. Aliased
835
+ * to the concrete VerboseLogger (core utility) so build.ts can pass its logger
836
+ * verbatim without a structural-variance mismatch on `step`.
837
+ */
838
+ export type BuildStepLogger = VerboseLogger;
839
+
840
+ /** A build step runs during `pnext build` (action discovery/bundling, manifest writes). */
841
+ export type BuildStep = ((ctx: BuildStepContext) => Promise<void>) & {
842
+ /**
843
+ * Opt in to running under the route-facts scan instead of after it. An early step may read `ctx.config`
844
+ * and route *paths* only - the facts (imports, use-client, CSS) are still being materialized while it runs.
845
+ */
846
+ early?: boolean;
847
+ /**
848
+ * Gate the build: run serially before any artifact-producing stage, so this
849
+ * step's diagnostics always precede a downstream failure it would have
850
+ * explained. Non-gate steps run concurrently with the client bundle stage.
851
+ */
852
+ gate?: boolean;
853
+ };
854
+ export type BuildCompleteHook = (ctx: {
855
+ config: ResolvedConfig;
856
+ manifest: BuildManifest;
857
+ log: BuildStepLogger;
858
+ }) => Promise<void>;
859
+
860
+ export interface CacheLifeStashLike {
861
+ expireSeconds?: number;
862
+ staleSeconds?: number;
863
+ revalidateSeconds?: number;
864
+ }
865
+
866
+ export interface SegmentPrefetchTree {
867
+ tree: SegmentTreeNodeLike;
868
+ staleTime: number;
869
+ isStatic: boolean;
870
+ }
871
+
872
+ export interface SegmentTreeNodeLike {
873
+ name: string;
874
+ param: null | { type: 'd' | 'c' | 'oc'; key: string | null; siblings: string[] | null };
875
+ slots: Record<string, SegmentTreeNodeLike> | null;
876
+ prefetchHints: number;
877
+ sizeBytes?: number;
878
+ segmentPath?: string;
879
+ }
880
+
881
+ export interface SegmentMetaLike {
882
+ status: number;
883
+ staleTime: number;
884
+ postponed: boolean;
885
+ segmentPaths: string[];
886
+ segmentSizes?: Record<string, number>;
887
+ inlinedSegmentPaths?: string[];
888
+ prefetchHints?: Record<string, number>;
889
+ }
890
+
891
+ /**
892
+ * Opaque vary-params result of a tracked build render. Core reads only
893
+ * `params`/`search` (to decide whether the set is worth persisting); the full
894
+ * shape lives in compat.
895
+ */
896
+ export interface BuildResponseVary {
897
+ params: string[];
898
+ search: boolean;
899
+ }
900
+
901
+ export interface BuildCompatExtensions {
902
+ /**
903
+ * Run a build render inside a vary-params tracking scope. Pure-core default:
904
+ * no tracking (`vary` undefined).
905
+ */
906
+ withVaryParamsTracking: <T>(
907
+ produce: () => Promise<T>,
908
+ ) => Promise<{ value: T; vary?: BuildResponseVary }>;
909
+ /** The wire vary names for one segment kind of a tracked render. */
910
+ varyNamesFor: (vary: BuildResponseVary, kind: 'body' | 'head' | 'page' | 'layout') => string[];
911
+ warnMetadataIssues: (input: {
912
+ appPath: string;
913
+ routes: RouteManifestEntry[];
914
+ staticMetadataFiles: StaticMetadataFile[];
915
+ }) => Promise<void>;
916
+ nextOutputExport: () => boolean;
917
+ /** The configured `output` mode when it blocks `next start` ('export' | 'standalone'), else undefined. */
918
+ nextOutputMode: () => 'export' | 'standalone' | undefined;
919
+ nextScriptWorkersEnabled: () => boolean;
920
+ defaultExpireTimeSeconds: () => number | undefined;
921
+ takeCacheLifeStash: () => CacheLifeStashLike | undefined;
922
+ /**
923
+ * The `Link` header value for the font preloads stashed during the current
924
+ * render work unit (next/font), or undefined when none. A static prerender
925
+ * never runs the response finalizer that would otherwise flush these, so the
926
+ * build bakes the header into the manifest. Must be called inside the render
927
+ * work unit before it unwinds. No-op default for pure-core apps.
928
+ */
929
+ takeFontLinkHeader: () => string | undefined;
930
+ normalizeStaticParamsModule: (module: Record<string, unknown>) => Record<string, unknown>;
931
+ defaultDynamicStaleTimeSeconds: number;
932
+ defaultStaticStaleTimeSeconds: number;
933
+ buildRootTreePrefetch: (options: {
934
+ pathname: string;
935
+ isStatic: boolean;
936
+ staleTimeSeconds?: number;
937
+ buildId?: string;
938
+ routeId?: string;
939
+ bodySizeBytes?: number;
940
+ inlineBudgetBytes?: number;
941
+ runtimePrefetch?: boolean;
942
+ postponed?: boolean;
943
+ }) => SegmentPrefetchTree;
944
+ rootTreePrefetchText: (payload: SegmentPrefetchTree, format?: 'json' | 'flight') => string;
945
+ buildSegmentMeta: (options: {
946
+ status: number;
947
+ staleTime: number;
948
+ postponed: boolean;
949
+ bodySizeBytes?: number;
950
+ inlineBudgetBytes?: number;
951
+ prefetchHints?: Record<string, number>;
952
+ }) => SegmentMetaLike;
953
+ /**
954
+ * `--experimental-build-mode generate` prerender diagnostics: Next's exact
955
+ * blocking-prerender error block for a cacheComponents route that cannot be
956
+ * prerendered, or undefined when the route is fine. Pure-core default: no
957
+ * diagnostics (generate never fails).
958
+ */
959
+ diagnoseCacheComponentsPrerender: (input: {
960
+ route: string;
961
+ pageFile: string;
962
+ appPath: string;
963
+ debugPrerender: boolean;
964
+ }) => string | undefined;
965
+ /**
966
+ * The shared "Error occurred prerendering page" + export-error footer.
967
+ * `omitErrorLine` drops the leading error line for diagnostics that already
968
+ * printed it (see `diagnosticLeadsWithErrorLine`).
969
+ */
970
+ prerenderFailureFooter: (
971
+ route: string,
972
+ debugPrerender: boolean,
973
+ omitErrorLine?: boolean,
974
+ ) => string;
975
+ /** Whether a diagnostic block already opens with the prerender-error line. */
976
+ diagnosticLeadsWithErrorLine: (diagnostic: string) => boolean;
977
+ /**
978
+ * Persist build inputs the SERVING runtime needs (compat: the
979
+ * `--debug-prerender` flag, which selects the shape of the runtime
980
+ * 'use cache' error log). Pure-core default: no-op.
981
+ */
982
+ recordBuildFlags: (outPath: string, debugPrerender: boolean) => void;
983
+ segmentDir: (outPath: string, routeId: string) => string;
984
+ treeSegmentFile: (outPath: string, routeId: string) => string;
985
+ bodySegmentFile: (outPath: string, routeId: string) => string;
986
+ segmentMetaFile: (outPath: string, routeId: string) => string;
987
+ }
988
+
989
+ /** Which entry point is running the init hooks (`pnext build` vs a server). */
990
+ export interface InitHookContext {
991
+ build: boolean;
992
+ }
993
+
994
+ /**
995
+ * Work a build step kicks off and does NOT await - it runs alongside bundling and prerendering, and the CLI
996
+ * awaits it just before the summary, reporting it as its own metric line. Compat registers the typecheck here.
997
+ */
998
+ export interface BuildParallelPhase {
999
+ /** Label for the reported line ("Typecheck"). */
1000
+ name: string;
1001
+ /** Resolves with the phase's OWN elapsed ms (not the time spent awaiting it). */
1002
+ run: Promise<number>;
1003
+ }
1004
+
1005
+ export interface BuildExtensions {
1006
+ /** Ordered build steps (compat registers action discovery + server-reference manifest). */
1007
+ steps: BuildStep[];
1008
+ /** Ordered hooks run after manifest.json is written. */
1009
+ completeHooks: BuildCompleteHook[];
1010
+ /** Init hooks run at the start of build + start (compat registers installCompatFetchCache). */
1011
+ initHooks: ((config: ResolvedConfig, context: InitHookContext) => void)[];
1012
+ /** Background phases started by a build step, awaited before the summary. */
1013
+ parallelPhases: BuildParallelPhase[];
1014
+ compat: BuildCompatExtensions;
1015
+ }
1016
+
1017
+ function createBuildExtensions(): BuildExtensions {
1018
+ const compat: BuildCompatExtensions = {
1019
+ withVaryParamsTracking: async produce => ({ value: await produce() }),
1020
+ varyNamesFor: () => [],
1021
+ warnMetadataIssues: () => Promise.resolve(),
1022
+ nextOutputExport: () => false,
1023
+ nextOutputMode: () => undefined,
1024
+ nextScriptWorkersEnabled: () => false,
1025
+ defaultExpireTimeSeconds: () => undefined,
1026
+ takeCacheLifeStash: () => undefined,
1027
+ takeFontLinkHeader: () => undefined,
1028
+ normalizeStaticParamsModule: module => module,
1029
+ defaultDynamicStaleTimeSeconds: 30,
1030
+ defaultStaticStaleTimeSeconds: 300,
1031
+ buildRootTreePrefetch: options => {
1032
+ const staleTime =
1033
+ options.staleTimeSeconds ??
1034
+ (options.isStatic
1035
+ ? compat.defaultStaticStaleTimeSeconds
1036
+ : compat.defaultDynamicStaleTimeSeconds);
1037
+ const bodySize = options.bodySizeBytes ?? 0;
1038
+ const budget = options.inlineBudgetBytes ?? 32 * 1024;
1039
+ const canInlineBody = options.isStatic && !options.postponed && bodySize <= budget;
1040
+ return {
1041
+ tree: {
1042
+ name: '',
1043
+ param: null,
1044
+ slots: {
1045
+ children: {
1046
+ name: '__PAGE__',
1047
+ param: null,
1048
+ slots: null,
1049
+ prefetchHints: canInlineBody ? 0b10000000 : 0b100000000,
1050
+ ...(options.bodySizeBytes !== undefined ? { sizeBytes: options.bodySizeBytes } : {}),
1051
+ ...(canInlineBody ? {} : { segmentPath: '/' }),
1052
+ },
1053
+ },
1054
+ prefetchHints: 0b10000 | (canInlineBody ? 0b1000000 : 0),
1055
+ },
1056
+ staleTime,
1057
+ isStatic: options.isStatic,
1058
+ };
1059
+ },
1060
+ rootTreePrefetchText: (payload, format = 'json') => {
1061
+ const json = JSON.stringify(payload);
1062
+ return format === 'flight' ? `0:${json}` : json;
1063
+ },
1064
+ buildSegmentMeta: options => {
1065
+ const inlined =
1066
+ !options.postponed &&
1067
+ options.bodySizeBytes !== undefined &&
1068
+ options.bodySizeBytes <= (options.inlineBudgetBytes ?? 32 * 1024);
1069
+ return {
1070
+ status: options.status,
1071
+ staleTime: options.staleTime,
1072
+ postponed: options.postponed,
1073
+ segmentPaths: ['/_tree', '/'],
1074
+ ...(options.bodySizeBytes !== undefined ? { segmentSizes: { '/': options.bodySizeBytes } } : {}),
1075
+ ...(inlined ? { inlinedSegmentPaths: ['/'] } : {}),
1076
+ ...(options.prefetchHints ? { prefetchHints: options.prefetchHints } : {}),
1077
+ };
1078
+ },
1079
+ diagnoseCacheComponentsPrerender: () => undefined,
1080
+ prerenderFailureFooter: () => '',
1081
+ diagnosticLeadsWithErrorLine: () => false,
1082
+ recordBuildFlags: () => undefined,
1083
+ segmentDir: (outPath, routeId) => `${outPath}/segments/${routeId}`,
1084
+ treeSegmentFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/_tree.segment.rsc`,
1085
+ bodySegmentFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/index.segment.rsc`,
1086
+ segmentMetaFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/route.segment.meta`,
1087
+ };
1088
+ return { steps: [], completeHooks: [], initHooks: [], parallelPhases: [], compat };
1089
+ }
1090
+
1091
+ export function getBuildExtensions(): BuildExtensions {
1092
+ return activeHost.build;
1093
+ }
1094
+
1095
+ export function setBuildCompatExtensions(overrides: Partial<BuildCompatExtensions>): void {
1096
+ Object.assign(activeHost.build.compat, overrides);
1097
+ }
1098
+
1099
+ export function registerBuildSteps(...steps: BuildStep[]): void {
1100
+ activeHost.build.steps.push(...steps);
1101
+ }
1102
+
1103
+ export function registerBuildCompleteHooks(...hooks: BuildCompleteHook[]): void {
1104
+ activeHost.build.completeHooks.push(...hooks);
1105
+ }
1106
+
1107
+ /**
1108
+ * Register in-flight background work. The rejection is parked here so a phase
1109
+ * that fails long before the CLI awaits it never surfaces as an unhandled
1110
+ * rejection; `buildParallelPhaseError` lets the CLI prefer it over a downstream
1111
+ * failure it caused (a type error also breaking the prerender, say).
1112
+ */
1113
+ export function registerBuildParallelPhases(...phases: BuildParallelPhase[]): void {
1114
+ for (const phase of phases) {
1115
+ phase.run.catch((error: unknown) => {
1116
+ parallelPhaseErrors.set(phase, error);
1117
+ });
1118
+ }
1119
+ activeHost.build.parallelPhases.push(...phases);
1120
+ }
1121
+
1122
+ const parallelPhaseErrors = new WeakMap<BuildParallelPhase, unknown>();
1123
+
1124
+ /** Drop the previous build's phases (one process can run several builds). */
1125
+ export function clearBuildParallelPhases(): void {
1126
+ activeHost.build.parallelPhases.length = 0;
1127
+ }
1128
+
1129
+ /** The first registered phase that has ALREADY failed, if any. */
1130
+ export function buildParallelPhaseError(): unknown {
1131
+ for (const phase of activeHost.build.parallelPhases) {
1132
+ if (parallelPhaseErrors.has(phase)) return parallelPhaseErrors.get(phase);
1133
+ }
1134
+ return undefined;
1135
+ }
1136
+
1137
+ export function registerInitHooks(
1138
+ ...hooks: ((config: ResolvedConfig, context: InitHookContext) => void)[]
1139
+ ): void {
1140
+ activeHost.build.initHooks.push(...hooks);
1141
+ }
1142
+
1143
+ /** Run every registered init hook (core calls at build + start startup). */
1144
+ export function runInitHooks(config: ResolvedConfig, context: InitHookContext = { build: false }): void {
1145
+ for (const hook of activeHost.build.initHooks) hook(config, context);
1146
+ }
1147
+
1148
+ // Routing extensions - compat registers extra convention filenames (with their boundary semantics)
1149
+ // and source usage-detection predicates that mark a route as request-dependent. Core scans its own
1150
+ // generic conventions and reads its own simple exports; the Next-only conventions
1151
+ // (forbidden/unauthorized) and next/* import detection move here.
1152
+
1153
+ /**
1154
+ * An extra convention filename compat wants core routing to discover alongside
1155
+ * its own special files. `boundary: true` marks it a render boundary the route
1156
+ * scanner treats like error/not-found (collected into a route's special files).
1157
+ */
1158
+ export interface RouteConvention {
1159
+ name: string;
1160
+ boundary: boolean;
1161
+ }
1162
+
1163
+ /**
1164
+ * A usage-detection predicate: given a source module's text, return true when it
1165
+ * makes the route request-dependent (dynamic). Compat registers the next/*
1166
+ * import detectors (next/headers, next/navigation request hooks, next/server
1167
+ * connection()). Core runs these in addition to its own generic checks.
1168
+ */
1169
+ export type UsageDetector = (source: string) => boolean;
1170
+
1171
+ /** A route module and its rewritten source, supplied across the full import graph. */
1172
+ export interface RouteDependencySource {
1173
+ file: string;
1174
+ source: string;
1175
+ }
1176
+
1177
+ /** Generic route facts an extension can classify from a transitive module graph. */
1178
+ export interface RouteDependencyContext {
1179
+ kind: 'page' | 'handler';
1180
+ files: readonly RouteDependencySource[];
1181
+ }
1182
+
1183
+ /** Extension-owned classification. Labels are opaque to core. */
1184
+ export interface RouteDependencyClassification {
1185
+ usesRequest?: boolean;
1186
+ }
1187
+
1188
+ export type RouteDependencyClassifier = (
1189
+ context: RouteDependencyContext,
1190
+ ) => RouteDependencyClassification | undefined;
1191
+
1192
+ /**
1193
+ * A source predicate that requires a client entry, paired with the REASON it fired. Reasons are the
1194
+ * substrate the client build gates feature regions on - a page that ships the runtime only for
1195
+ * `<Link>` needs none of the action machinery - so every detector says which fact it observed.
1196
+ */
1197
+ export interface ClientEntryDetector {
1198
+ reason: ClientEntryReason;
1199
+ detect: UsageDetector;
1200
+ }
1201
+
1202
+ export interface RoutingExtensions {
1203
+ /** Extra convention filenames compat wants discovered (forbidden/unauthorized/...). */
1204
+ conventions: RouteConvention[];
1205
+ /** Predicates that mark a page/layout source as request-dependent. */
1206
+ usageDetection: UsageDetector[];
1207
+ /** Classifiers that inspect a route's complete module graph. */
1208
+ dependencyClassification: RouteDependencyClassifier[];
1209
+ /** Reason-tagged predicates that require a client entry for a server-only page. */
1210
+ clientEntryDetection: ClientEntryDetector[];
1211
+ /**
1212
+ * Extra file extensions (without the leading dot) the route scanner should
1213
+ * treat as page/convention files, ADDITIVE to core's built-in
1214
+ * tsx/ts/jsx/js/mjs. Compat (next/mdx) registers `mdx`/`md` so `page.mdx` +
1215
+ * top-level `.mdx` pages resolve. Consumed lazily by src/routing/routes.ts so
1216
+ * registration at bootstrap (before scanRoutes) is honored. Order-preserving,
1217
+ * de-duplicated against the base list.
1218
+ */
1219
+ pageExtensions: string[];
1220
+ /**
1221
+ * Why every page route must ship the client router entry, even a purely
1222
+ * server-rendered one with no client references. Next always emits its
1223
+ * app-router bootstrap per page (so instrumentation-client and hydration run
1224
+ * on every document) and registers `compat-parity` here; a pure-core app
1225
+ * keeps the lean "no client code, no bundle" default with an empty set.
1226
+ */
1227
+ alwaysClientEntryReasons: Set<ClientEntryReason>;
1228
+ }
1229
+
1230
+ function createRoutingExtensions(): RoutingExtensions {
1231
+ return {
1232
+ conventions: [],
1233
+ usageDetection: [],
1234
+ dependencyClassification: [],
1235
+ clientEntryDetection: [],
1236
+ pageExtensions: [],
1237
+ alwaysClientEntryReasons: new Set(),
1238
+ };
1239
+ }
1240
+
1241
+ export function getRoutingExtensions(): RoutingExtensions {
1242
+ return activeHost.routing;
1243
+ }
1244
+
1245
+ /** Register extra route convention filenames (compat: forbidden/unauthorized). */
1246
+ export function registerRouteConventions(...conventions: RouteConvention[]): void {
1247
+ activeHost.routing.conventions.push(...conventions);
1248
+ }
1249
+
1250
+ /** Register request-dependency usage detectors (compat: next/* import patterns). */
1251
+ export function registerUsageDetectors(...detectors: UsageDetector[]): void {
1252
+ activeHost.routing.usageDetection.push(...detectors);
1253
+ }
1254
+
1255
+ /** Register transitive route-dependency classifiers. */
1256
+ export function registerRouteDependencyClassifiers(...classifiers: RouteDependencyClassifier[]): void {
1257
+ activeHost.routing.dependencyClassification.push(...classifiers);
1258
+ }
1259
+
1260
+ /** Register reason-tagged client-entry detectors (compat: server-action forms, next/form). */
1261
+ export function registerClientEntryDetectors(...detectors: ClientEntryDetector[]): void {
1262
+ activeHost.routing.clientEntryDetection.push(...detectors);
1263
+ clientEntryReasonCache.clear();
1264
+ }
1265
+
1266
+ /**
1267
+ * Register extra page/convention file extensions (without the leading dot),
1268
+ * additive to core's built-in tsx/ts/jsx/js/mjs (compat: next/mdx registers
1269
+ * `mdx`/`md`). Duplicates (including against the core base list) are ignored by
1270
+ * the routing consumer, so calling with an already-present extension is a no-op.
1271
+ */
1272
+ // Loadable = compiles through the server module graph. Superset of page
1273
+ // extensions: an imported `.md` is loadable but not routable (Next: @next/mdx
1274
+ // compiles imported .md while pageExtensions may list only mdx). Listeners let
1275
+ // consumers that latched the set (the server load plugin's per-root filter
1276
+ // regex) extend it instead of leaving late extensions to Bun's file loader
1277
+ // (default export = the file path, rendered as a bogus JSX tag).
1278
+ // Listeners stay module-global: a consumer latches the set once (the server
1279
+ // load plugin's per-root filter regex) and must keep hearing about it across a
1280
+ // host swap, which changes the set exactly like a registration does.
1281
+ const loadableExtensionListeners: (() => void)[] = [];
1282
+
1283
+ export function onLoadableExtensionsChanged(listener: () => void): void {
1284
+ loadableExtensionListeners.push(listener);
1285
+ }
1286
+
1287
+ export function registerLoadableExtensions(...extensions: string[]): void {
1288
+ const loadable = activeHost.loadable;
1289
+ const fresh = [...new Set(extensions)].filter(ext => !loadable.includes(ext));
1290
+ if (fresh.length === 0) return;
1291
+ loadable.push(...fresh);
1292
+ for (const listener of loadableExtensionListeners) listener();
1293
+ }
1294
+
1295
+ /** Page extensions plus import-only loadable extensions, deduped. */
1296
+ export function extraLoadableExtensions(): string[] {
1297
+ return [...new Set([...activeHost.routing.pageExtensions, ...activeHost.loadable])];
1298
+ }
1299
+
1300
+ export function registerPageExtensions(...extensions: string[]): void {
1301
+ const { pageExtensions } = activeHost.routing;
1302
+ const fresh = [...new Set(extensions)].filter(ext => !pageExtensions.includes(ext));
1303
+ if (fresh.length === 0) return;
1304
+ pageExtensions.push(...fresh);
1305
+ for (const listener of loadableExtensionListeners) listener();
1306
+ }
1307
+
1308
+ /** Record a reason every page route must ship the client router entry. */
1309
+ export function registerAlwaysClientEntryReason(reason: ClientEntryReason): void {
1310
+ activeHost.routing.alwaysClientEntryReasons.add(reason);
1311
+ }
1312
+
1313
+ /** Why every page ships the router entry; empty for a pure-core app. */
1314
+ export function alwaysClientEntryReasons(): ClientEntryReason[] {
1315
+ return [...activeHost.routing.alwaysClientEntryReasons];
1316
+ }
1317
+
1318
+ /** Extra page-file extensions compat registered, in registration order. */
1319
+ export function extraPageExtensions(): string[] {
1320
+ return activeHost.routing.pageExtensions;
1321
+ }
1322
+
1323
+ /** The extra boundary-convention names core routing must discover as special files. */
1324
+ export function extraBoundaryConventionNames(): string[] {
1325
+ return activeHost.routing.conventions.filter(c => c.boundary).map(c => c.name);
1326
+ }
1327
+
1328
+ /** True when any registered usage detector marks `source` as request-dependent. */
1329
+ export function sourceUsesRegisteredRequestApi(source: string): boolean {
1330
+ return activeHost.routing.usageDetection.some(detect => detect(source));
1331
+ }
1332
+
1333
+ /** Merge extension classifications for a route's complete module graph. */
1334
+ export function classifyRouteDependencies(
1335
+ context: RouteDependencyContext,
1336
+ ): RouteDependencyClassification {
1337
+ const classification: RouteDependencyClassification = {};
1338
+ for (const classify of activeHost.routing.dependencyClassification) {
1339
+ const result = classify(context);
1340
+ if (!result) continue;
1341
+ if (result.usesRequest) classification.usesRequest = true;
1342
+ }
1343
+ return classification;
1344
+ }
1345
+
1346
+ /** Every reason a registered detector requires a client entry for this source. */
1347
+ export function sourceClientEntryReasons(source: string): ClientEntryReason[] {
1348
+ const key = `${source.length}\0${Bun.hash(source).toString(36)}`;
1349
+ const cached = clientEntryReasonCache.get(key);
1350
+ if (cached) return cached;
1351
+ const reasons: ClientEntryReason[] = [];
1352
+ for (const { reason, detect } of activeHost.routing.clientEntryDetection) {
1353
+ if (!reasons.includes(reason) && detect(source)) reasons.push(reason);
1354
+ }
1355
+ clientEntryReasonCache.set(key, reasons);
1356
+ return reasons;
1357
+ }
1358
+
1359
+ // ---------------------------------------------------------------------------
1360
+ // Proxy extensions — populated by compat/next proxy support.
1361
+ // ---------------------------------------------------------------------------
1362
+
1363
+ export interface ProxyExtensions {
1364
+ /** File basenames core should consider as request proxy entrypoints. */
1365
+ names: readonly string[];
1366
+ /** Optional validation hook for framework-specific proxy diagnostics. */
1367
+ validateFiles: (config: ResolvedConfig) => Promise<void>;
1368
+ /** Pick the handler export from a loaded proxy module. */
1369
+ handlerExport: (module: Record<string, unknown>) => unknown;
1370
+ /** Preserve the original proxy URL and Flight headers for advanced routing. */
1371
+ skipUrlNormalize: () => boolean;
1372
+ /** Framework locale derived from the proxy request URL. */
1373
+ locale: (url: URL) => string;
1374
+ /** Notify compat before an external proxy rewrite is fetched. */
1375
+ onExternalRewrite: (request: Request) => void;
1376
+ /**
1377
+ * Max request-body bytes buffered on a proxied request before truncation
1378
+ * (compat maps experimental.proxyClientMaxBodySize; Next's default is 10MB).
1379
+ */
1380
+ clientMaxBodySize: () => number;
1381
+ }
1382
+
1383
+ function createProxyExtensions(): ProxyExtensions {
1384
+ return {
1385
+ names: ['proxy'],
1386
+ validateFiles: () => Promise.resolve(),
1387
+ handlerExport: module => module.proxy ?? module.default,
1388
+ skipUrlNormalize: () => false,
1389
+ locale: () => '',
1390
+ onExternalRewrite: request => void request,
1391
+ clientMaxBodySize: () => 10 * 1024 * 1024,
1392
+ };
1393
+ }
1394
+
1395
+ export function getProxyExtensions(): ProxyExtensions {
1396
+ return activeHost.proxy;
1397
+ }
1398
+
1399
+ export function setProxyExtensions(overrides: Partial<ProxyExtensions>): void {
1400
+ Object.assign(activeHost.proxy, overrides);
1401
+ }
1402
+
1403
+ // Bundler extensions - populated by compat/cache/use-cache-transform, compat/actions/rewrite and
1404
+ // compat/actions/client-plugin.
1405
+
1406
+ /**
1407
+ * A server-source transform applied after core's own transforms. Compat registers rewriteUseCacheSource and
1408
+ * rewriteInlineActionTags. Called with the source, its file path, and optionally the project root - several
1409
+ * compat transforms accept a root for id derivation, so it is threaded through.
1410
+ */
1411
+ export interface ServerSourceTransform {
1412
+ (source: string, file: string, root?: string): string;
1413
+ /**
1414
+ * Cheap trigger tokens (`includes`, or a `test` for the few shapes whitespace makes non-literal). The
1415
+ * transform is skipped when the source matches none of them, so it must be a SUPERSET of the transform's
1416
+ * own exact gate - an empty array means "configured off, never fires". Attach with `withSniff`. Absent
1417
+ * means ungated: the transform always runs and disables the chain gate.
1418
+ */
1419
+ sniff?: readonly SniffToken[];
1420
+ }
1421
+
1422
+ export type SniffToken = string | RegExp;
1423
+
1424
+ /**
1425
+ * A client-source transform that must await IO (compat: worker bundling emits a
1426
+ * chunk before it can substitute the URL). Runs ahead of the sync chain, so a
1427
+ * pass that consumes a token the sync passes rewrite (`import.meta.url`) still
1428
+ * sees the original source.
1429
+ */
1430
+ export interface AsyncSourceTransform {
1431
+ (source: string, file: string, root?: string): Promise<string>;
1432
+ sniff?: readonly SniffToken[];
1433
+ }
1434
+
1435
+ export interface BundlerExtensions {
1436
+ /** Ordered source transforms that must run before generic server transforms. */
1437
+ serverSourcePreTransforms: ServerSourceTransform[];
1438
+ /** Ordered server-source transforms (compat: use-cache + inline-action tags). */
1439
+ serverSourceTransforms: ServerSourceTransform[];
1440
+ /** Ordered client-source transforms (compat: next/font + next/root-params). */
1441
+ clientSourceTransforms: ServerSourceTransform[];
1442
+ /** Awaitable client-source transforms run before `clientSourceTransforms`. */
1443
+ clientSourceAsyncPreTransforms: AsyncSourceTransform[];
1444
+ /**
1445
+ * Transforms applied to already-BUNDLED server output (vendor bundles). Only the passes whose trigger
1446
+ * survives bundling belong here - a package can ship a `'use cache'` function, but its imports and aliases
1447
+ * were already resolved by the bundler and its `define` constants inlined by esbuild.
1448
+ */
1449
+ bundledSourceTransforms: ServerSourceTransform[];
1450
+ /** esbuild plugins appended to every client build (compat: action client stub). */
1451
+ clientEsbuildPlugins: (config: ResolvedConfig) => Plugin[];
1452
+ /** esbuild plugins appended to every server build (compat: action client stub). */
1453
+ serverEsbuildPlugins: (config: ResolvedConfig, options?: ServerEsbuildPluginOptions) => Plugin[];
1454
+ /**
1455
+ * Entry-point rewrite consulted where a server bundle's entry is already
1456
+ * resolved (compat: the CJS default-interop marker, so its plugin never
1457
+ * needs a catch-all onResolve). undefined = use the specifier as-is.
1458
+ */
1459
+ serverBundleEntry: (specifier: string, resolveDir: string, resolved?: string) => string | undefined;
1460
+ /**
1461
+ * esbuild `define` constants for every server compile (compat: compiler.define + compiler.defineServer).
1462
+ * Lexical by construction - unlike a textual pass it never rewrites inside string literals or comments.
1463
+ */
1464
+ serverDefines: () => Record<string, string>;
1465
+ /** Same, for the client graph (compat: compiler.define; defineServer -> undefined). */
1466
+ clientDefines: () => Record<string, string>;
1467
+ /**
1468
+ * Files injected into every client entry graph for their side effects
1469
+ * (compat: core-js polyfills under experimental.swcEnvOptions).
1470
+ */
1471
+ clientInjects: () => string[];
1472
+ /** Resolve compat-owned package edges while scanning route dependencies. */
1473
+ resolveRouteDependency: (root: string, fromFile: string, specifier: string) => string | undefined;
1474
+ /**
1475
+ * Extra `pure` targets (e.g. `console.log`) for the client esbuild `pure`
1476
+ * option, so calls with unused results are dropped (compat: compiler.removeConsole).
1477
+ */
1478
+ clientPureFunctions: (config: ResolvedConfig) => string[];
1479
+ /**
1480
+ * Standalone static chunks written verbatim into the client `chunks/` dir,
1481
+ * independent of the esbuild entry graph (compat: the no-module polyfills
1482
+ * chunk). Pure-core apps emit none.
1483
+ */
1484
+ staticClientChunks: (config: ResolvedConfig) => { name: string; contents: string }[];
1485
+ }
1486
+
1487
+ function createBundlerExtensions(): BundlerExtensions {
1488
+ return {
1489
+ serverSourcePreTransforms: [],
1490
+ serverSourceTransforms: [],
1491
+ clientSourceTransforms: [],
1492
+ clientSourceAsyncPreTransforms: [],
1493
+ bundledSourceTransforms: [],
1494
+ clientEsbuildPlugins: () => [],
1495
+ serverEsbuildPlugins: () => [],
1496
+ serverBundleEntry: () => undefined,
1497
+ serverDefines: () => ({}),
1498
+ clientDefines: () => ({}),
1499
+ clientInjects: () => [],
1500
+ resolveRouteDependency: () => undefined,
1501
+ clientPureFunctions: () => [],
1502
+ staticClientChunks: () => [],
1503
+ };
1504
+ }
1505
+
1506
+ export function getBundlerExtensions(): BundlerExtensions {
1507
+ return activeHost.bundler;
1508
+ }
1509
+
1510
+ /**
1511
+ * esbuild `define` for a server compile (`build` or `transform`). Spread into
1512
+ * the options; unconfigured apps get `{}` and esbuild sees no `define` at all.
1513
+ */
1514
+ export function serverDefineOptions(): { define?: Record<string, string> } {
1515
+ const define = activeHost.bundler.serverDefines();
1516
+ return Object.keys(define).length > 0 ? { define } : {};
1517
+ }
1518
+
1519
+ export function registerServerSourceTransforms(...transforms: ServerSourceTransform[]): void {
1520
+ activeHost.bundler.serverSourceTransforms.push(...transforms);
1521
+ activeHost.serverSniff = undefined;
1522
+ }
1523
+
1524
+ export function registerServerSourcePreTransforms(...transforms: ServerSourceTransform[]): void {
1525
+ activeHost.bundler.serverSourcePreTransforms.push(...transforms);
1526
+ activeHost.serverSniff = undefined;
1527
+ }
1528
+
1529
+ export function registerClientSourceTransforms(...transforms: ServerSourceTransform[]): void {
1530
+ activeHost.bundler.clientSourceTransforms.push(...transforms);
1531
+ }
1532
+
1533
+ export function registerClientSourceAsyncPreTransforms(
1534
+ ...transforms: AsyncSourceTransform[]
1535
+ ): void {
1536
+ activeHost.bundler.clientSourceAsyncPreTransforms.push(...transforms);
1537
+ }
1538
+
1539
+ export function registerBundledSourceTransforms(...transforms: ServerSourceTransform[]): void {
1540
+ activeHost.bundler.bundledSourceTransforms.push(...transforms);
1541
+ }
1542
+
1543
+ /**
1544
+ * Tag a transform with the cheap trigger tokens that gate it. Wraps rather than
1545
+ * mutates, so a transform registered on both the server and client chains can
1546
+ * be tagged independently.
1547
+ */
1548
+ export function withSniff<T extends ServerSourceTransform | AsyncSourceTransform>(
1549
+ sniff: readonly SniffToken[],
1550
+ transform: T,
1551
+ ): T {
1552
+ return Object.assign(
1553
+ (source: string, file: string, root?: string) => transform(source, file, root),
1554
+ { sniff },
1555
+ ) as T;
1556
+ }
1557
+
1558
+ function sniffHit(source: string, sniff: readonly SniffToken[]): boolean {
1559
+ for (const token of sniff) {
1560
+ if (typeof token === 'string' ? source.includes(token) : token.test(source)) return true;
1561
+ }
1562
+ return false;
1563
+ }
1564
+
1565
+ /**
1566
+ * The union of every registered transform's tokens: the whole server chain can
1567
+ * bail on one deduped scan instead of re-testing per pass. Derived from the
1568
+ * registry and regenerated on every registration, so a transform registered
1569
+ * without a sniff (`always: true` here) can never be silently skipped. Cached
1570
+ * on the host, so a swap starts from the new host's own chain.
1571
+ */
1572
+ function serverSourceSniff() {
1573
+ const host = activeHost;
1574
+ if (host.serverSniff) return host.serverSniff;
1575
+ const tokens = new Set<string>();
1576
+ for (const transform of [
1577
+ ...host.bundler.serverSourcePreTransforms,
1578
+ ...host.bundler.serverSourceTransforms,
1579
+ ]) {
1580
+ if (!transform.sniff) return (host.serverSniff = { pattern: undefined });
1581
+ for (const token of transform.sniff) {
1582
+ tokens.add(`(?:${typeof token === 'string' ? escapeRegex(token) : token.source})`);
1583
+ }
1584
+ }
1585
+ // One alternation scan measured cheaper than N `includes` over the same
1586
+ // bytes. With no tokens at all (every transform configured off) the chain can
1587
+ // never fire, and `new RegExp('')` would match everything.
1588
+ return (host.serverSniff = { pattern: new RegExp(tokens.size ? [...tokens].join('|') : '(?!)') });
1589
+ }
1590
+
1591
+ /** Whether any registered server-source transform can possibly change `source`. */
1592
+ export function sourceNeedsServerTransforms(source: string): boolean {
1593
+ const { pattern } = serverSourceSniff();
1594
+ return !pattern || pattern.test(source);
1595
+ }
1596
+
1597
+ function applyTransforms(
1598
+ transforms: readonly ServerSourceTransform[],
1599
+ source: string,
1600
+ file: string,
1601
+ root?: string,
1602
+ ): string {
1603
+ let next = source;
1604
+ for (const transform of transforms) {
1605
+ // Gate on the running source, not the original: an earlier transform can
1606
+ // inject a later one's trigger (e.g. next/font emits next/root-params).
1607
+ if (transform.sniff && !sniffHit(next, transform.sniff)) continue;
1608
+ next = transform(next, file, root);
1609
+ }
1610
+ return next;
1611
+ }
1612
+
1613
+ export function setBundlerExtensions(
1614
+ overrides: Partial<
1615
+ Omit<
1616
+ BundlerExtensions,
1617
+ | 'serverSourcePreTransforms'
1618
+ | 'serverSourceTransforms'
1619
+ | 'clientSourceTransforms'
1620
+ | 'clientSourceAsyncPreTransforms'
1621
+ | 'bundledSourceTransforms'
1622
+ >
1623
+ >,
1624
+ ): void {
1625
+ Object.assign(activeHost.bundler, overrides);
1626
+ }
1627
+
1628
+ /** Apply every registered pre server-source transform in order. */
1629
+ export function applyServerSourcePreTransforms(source: string, file: string, root?: string): string {
1630
+ return applyTransforms(activeHost.bundler.serverSourcePreTransforms, source, file, root);
1631
+ }
1632
+
1633
+ /** Apply every registered server-source transform in order. */
1634
+ export function applyServerSourceTransforms(source: string, file: string, root?: string): string {
1635
+ return applyTransforms(activeHost.bundler.serverSourceTransforms, source, file, root);
1636
+ }
1637
+
1638
+ export function applyClientSourceTransforms(source: string, file: string, root?: string): string {
1639
+ return applyTransforms(activeHost.bundler.clientSourceTransforms, source, file, root);
1640
+ }
1641
+
1642
+ /** Whether any awaitable client pre-transform is registered at all. */
1643
+ export function hasClientSourceAsyncPreTransforms(): boolean {
1644
+ return activeHost.bundler.clientSourceAsyncPreTransforms.length > 0;
1645
+ }
1646
+
1647
+ export async function applyClientSourceAsyncPreTransforms(
1648
+ source: string,
1649
+ file: string,
1650
+ root?: string,
1651
+ ): Promise<string> {
1652
+ let next = source;
1653
+ for (const transform of activeHost.bundler.clientSourceAsyncPreTransforms) {
1654
+ if (transform.sniff && !sniffHit(next, transform.sniff)) continue;
1655
+ next = await transform(next, file, root);
1656
+ }
1657
+ return next;
1658
+ }
1659
+
1660
+ /** Apply every registered bundled-output transform in order. */
1661
+ export function applyBundledSourceTransforms(source: string, file: string): string {
1662
+ return applyTransforms(activeHost.bundler.bundledSourceTransforms, source, file);
1663
+ }
1664
+
1665
+ // ---------------------------------------------------------------------------
1666
+ // Import alias extensions — compat registers Next/React alias maps and missing
1667
+ // import diagnostics. Core owns only framework aliases.
1668
+ // ---------------------------------------------------------------------------
1669
+
1670
+ export interface ImportAliasExtensions {
1671
+ aliases: (config: ResolvedConfig, target: CompatAliasTarget) => Record<string, string>;
1672
+ clientSsrAliases: (config: ResolvedConfig) => Record<string, string>;
1673
+ missingImportError: (config: ResolvedConfig, specifier: string) => string | undefined;
1674
+ /**
1675
+ * Extra alias overrides layered on top of `aliases` for modules compiled under the true `react-server`
1676
+ * layer (currently proxy/middleware). Distinct from the base server target because pages/api keeps the
1677
+ * full-hooks `react` shim for backward compatibility while proxy/middleware must not expose client hooks
1678
+ * at all.
1679
+ */
1680
+ reactServerLayerAliases: (config: ResolvedConfig) => Record<string, string>;
1681
+ }
1682
+
1683
+ function createImportAliasExtensions(): ImportAliasExtensions {
1684
+ return {
1685
+ aliases: () => ({}),
1686
+ clientSsrAliases: () => ({}),
1687
+ missingImportError: () => undefined,
1688
+ reactServerLayerAliases: () => ({}),
1689
+ };
1690
+ }
1691
+
1692
+ export function getImportAliasExtensions(): ImportAliasExtensions {
1693
+ return activeHost.importAlias;
1694
+ }
1695
+
1696
+ export function setImportAliasExtensions(overrides: Partial<ImportAliasExtensions>): void {
1697
+ Object.assign(activeHost.importAlias, overrides);
1698
+ }
1699
+
1700
+ // ---------------------------------------------------------------------------
1701
+ // Proxy response protocol — core has generic control headers; compat registers
1702
+ // the Next middleware header names.
1703
+ // ---------------------------------------------------------------------------
1704
+
1705
+ export interface ProxyResponseProtocol {
1706
+ nextHeader: string;
1707
+ rewriteHeader: string;
1708
+ }
1709
+
1710
+ function createProxyResponseProtocol(): ProxyResponseProtocol {
1711
+ return {
1712
+ nextHeader: 'x-pnext-middleware-next',
1713
+ rewriteHeader: 'x-pnext-middleware-rewrite',
1714
+ };
1715
+ }
1716
+
1717
+ export function getProxyResponseProtocol(): ProxyResponseProtocol {
1718
+ return activeHost.proxyResponse;
1719
+ }
1720
+
1721
+ export function setProxyResponseProtocol(overrides: Partial<ProxyResponseProtocol>): void {
1722
+ Object.assign(activeHost.proxyResponse, overrides);
1723
+ }
1724
+
1725
+ export interface RouterProtocolExtensions {
1726
+ prefetchRequestHeaders: () => string[];
1727
+ /**
1728
+ * True when this request should receive only the route's static shell - Suspense/loading fallbacks in
1729
+ * place, streamed dynamic continuation cut (Next's partial prefetch: a `next-router-prefetch: 1` request
1730
+ * never runs dynamic data). Core consults it in renderPageResponse; the classification policy is compat's.
1731
+ */
1732
+ shellOnlyRequest: (request: Request, route: RouteManifestEntry) => boolean;
1733
+ }
1734
+
1735
+ function createRouterProtocolExtensions(): RouterProtocolExtensions {
1736
+ return {
1737
+ prefetchRequestHeaders: () => [],
1738
+ shellOnlyRequest: () => false,
1739
+ };
1740
+ }
1741
+
1742
+ export function getRouterProtocolExtensions(): RouterProtocolExtensions {
1743
+ return activeHost.routerProtocol;
1744
+ }
1745
+
1746
+ export function setRouterProtocolExtensions(overrides: Partial<RouterProtocolExtensions>): void {
1747
+ Object.assign(activeHost.routerProtocol, overrides);
1748
+ }
1749
+
1750
+ // Asset extensions - compat registers framework-specific handling for imported static assets (next/image
1751
+ // static image imports carrying real width/height/blurDataURL and a /_next/static/media asset URL). Core
1752
+ // keeps a generic behavior when no override is registered.
1753
+
1754
+ /**
1755
+ * Context for a static-asset module override. `sourcePath` is the asset file on
1756
+ * disk, `bytes` its contents. `emit(relativePath, bytes)` writes the asset to
1757
+ * the served public tree at that POSIX-relative path and returns its public URL
1758
+ * (e.g. `/_next/static/media/pic.<hash>.png`). A handler returns the ESM module
1759
+ * text an `import asset from './pic.png'` should evaluate to, or undefined to
1760
+ * let core apply its generic default.
1761
+ */
1762
+ export interface StaticAssetModuleContext {
1763
+ sourcePath: string;
1764
+ bytes: Uint8Array;
1765
+ emit: (relativePath: string, bytes: Uint8Array) => string;
1766
+ }
1767
+
1768
+ export interface StaticAssetPathContext {
1769
+ sourcePath: string;
1770
+ hash: string;
1771
+ base: string;
1772
+ ext: string;
1773
+ }
1774
+
1775
+ export interface AssetExtensions {
1776
+ /** URL path prefixes for emitted assets stored under `<out>/public`. */
1777
+ staticAssetPublicPrefixes: () => string[];
1778
+
1779
+ /**
1780
+ * POSIX relative public path for core's generic imported-asset module. Compat
1781
+ * registers Next's `/_next/static/media` layout.
1782
+ */
1783
+ staticAssetRelativePath: (context: StaticAssetPathContext) => string;
1784
+
1785
+ /**
1786
+ * Produce the ESM module text for an imported static asset, or undefined to fall back to core's generic
1787
+ * asset module. Compat (next/image) returns the static-image descriptor module; pure-core apps register
1788
+ * nothing. May return a Promise - every call site already awaits this seam - so a handler can run an async
1789
+ * pipeline.
1790
+ */
1791
+ staticAssetModule: (
1792
+ context: StaticAssetModuleContext,
1793
+ ) => string | undefined | Promise<string | undefined>;
1794
+
1795
+ /**
1796
+ * Whether a configured `turbopack.rules` loader chain claims this file. When
1797
+ * true, core's generic image/asset resolver (server + client static-asset
1798
+ * plugins) steps aside so the compat loader-rule plugin runs the chain
1799
+ * instead (e.g. a `*.svg` loader rule preempting the default image pipeline).
1800
+ * Compat registers `hasWebpackLoaderRuleFor`; pure-core apps keep the default.
1801
+ */
1802
+ hasLoaderRuleFor: (filePath: string) => boolean;
1803
+
1804
+ /**
1805
+ * Run the `turbopack.rules` loader chain configured for `specifier` (which may carry a query) as imported
1806
+ * from `importer`, and return the path of the module its output was materialized to - or undefined when no
1807
+ * rule claims it. Server modules compile one at a time and route files are inlined into a route bundle, so
1808
+ * a rule source never reaches a bundler `onLoad` with its importer's query intact; core's resolvers ask
1809
+ * this instead.
1810
+ */
1811
+ loaderRuleModule: (
1812
+ specifier: string,
1813
+ importer: string,
1814
+ ) => string | undefined | Promise<string | undefined>;
1815
+ }
1816
+
1817
+ function createAssetExtensions(): AssetExtensions {
1818
+ return {
1819
+ staticAssetPublicPrefixes: () => ['/__pnext/static/media/'],
1820
+ staticAssetRelativePath: ({ base, hash, ext }) => `__pnext/static/media/${base}.${hash}${ext}`,
1821
+ staticAssetModule: () => undefined,
1822
+ hasLoaderRuleFor: () => false,
1823
+ loaderRuleModule: () => undefined,
1824
+ };
1825
+ }
1826
+
1827
+ export function getAssetExtensions(): AssetExtensions {
1828
+ return activeHost.asset;
1829
+ }
1830
+
1831
+ export function setAssetExtensions(overrides: Partial<AssetExtensions>): void {
1832
+ Object.assign(activeHost.asset, overrides);
1833
+ }
1834
+
1835
+ // CSS extras (CORE - no-op defaults; compat populates via register/css-extras).
1836
+ //
1837
+ // Lets core's CSS build sites pull in compat CSS handling without a core-to-compat import edge. Every field
1838
+ // ships a working no-op default, so a pure-core / non-sass app is byte-identical. The single feature today
1839
+ // is sass/scss.
1840
+ //
1841
+ // Unlike the domains above, this registry is a module-level singleton rather than a host field: consumers
1842
+ // may hold the object across a host swap, so it is restored in place via restoreWithExtensionHost. Call
1843
+ // getCssExtensions() lazily inside the build functions, not at module top-level, so the registry is already
1844
+ // populated at bootstrap.
1845
+
1846
+ export interface CssExtensions {
1847
+ /** Extra CSS-like file extensions, including the dot. */
1848
+ extraCssExtensions: () => string[];
1849
+ /** Resolve a bare CSS dependency that normal route scanning leaves external. */
1850
+ resolveCssDependency: (root: string, fromFile: string, specifier: string) => string | undefined;
1851
+ /** Keep root not-found CSS with its fallback response instead of every matched route. */
1852
+ deferRootNotFoundCss: () => boolean;
1853
+ /** Replace built stylesheet links with document-head markup when compat requires it. */
1854
+ inlineStylesheets: (
1855
+ config: ResolvedConfig,
1856
+ options: { assetNames: string[]; dev: boolean; nonce?: string; prependCss?: string },
1857
+ ) => string[] | undefined;
1858
+ /** esbuild plugins for the CSS-chunk builds (compat: sass/scss loader). */
1859
+ cssChunkPlugins: () => Plugin[];
1860
+ /**
1861
+ * The scoped class-name map for a CSS-module file this registry handles
1862
+ * (`*.module.{scss,sass}`); undefined to defer to core (`*.module.css`).
1863
+ */
1864
+ resolveCssModule: (file: string) => Record<string, string> | undefined;
1865
+ /**
1866
+ * The ESM module text a client bundle should evaluate for a compat CSS-module
1867
+ * import (`export default <map>`); undefined to defer to core.
1868
+ */
1869
+ loadCssModuleForClient: (file: string) => string | undefined;
1870
+ /**
1871
+ * Partition each route's ordered `cssImports` into chunk slices (Next's CSS
1872
+ * chunking). Returns `routeId -> ordered segments`, each segment an ordered
1873
+ * slice of that route's imports; routes absent from the map (or with a single
1874
+ * segment) keep core's single-chunk behaviour.
1875
+ */
1876
+ planRouteCssChunks: (routes: { id: string; cssImports: string[] }[]) => Map<string, string[][]>;
1877
+ }
1878
+
1879
+ const cssExtensions: CssExtensions = {
1880
+ extraCssExtensions: () => [],
1881
+ resolveCssDependency: () => undefined,
1882
+ deferRootNotFoundCss: () => false,
1883
+ inlineStylesheets: () => undefined,
1884
+ cssChunkPlugins: () => [],
1885
+ resolveCssModule: () => undefined,
1886
+ loadCssModuleForClient: () => undefined,
1887
+ planRouteCssChunks: () => new Map(),
1888
+ };
1889
+
1890
+ restoreWithExtensionHost(cssExtensions);
1891
+
1892
+ export function getCssExtensions(): CssExtensions {
1893
+ return cssExtensions;
1894
+ }
1895
+
1896
+ export function setCssExtensions(overrides: Partial<CssExtensions>): void {
1897
+ Object.assign(cssExtensions, overrides);
1898
+ }