@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,173 @@
1
+ // after() - next/server (COMPAT).
2
+ //
3
+ // Registers work to run AFTER the response is fully sent. It builds on the core request work-unit:
4
+ // queueAfterTask enqueues onto the per-request after-queue that core flushes exactly once when the
5
+ // response closes - on every path (stream end, redirect(), notFound(), thrown error, client abort).
6
+ // Nested after() works because the flush runs the queue inside the work unit and re-drains newly
7
+ // enqueued tasks.
8
+ //
9
+ // When the surrounding platform provides the Vercel request-context waitUntil, each after task is also
10
+ // handed to it so a serverless platform keeps the invocation alive until it settles. When no work unit
11
+ // is active, the task runs on the microtask queue as a best-effort fallback.
12
+
13
+ import { captureCacheScope } from '../../cache/context';
14
+ import {
15
+ captureRequestScope,
16
+ currentRequest,
17
+ getWorkUnit,
18
+ queueAfterTask,
19
+ setPhase,
20
+ } from '../../request/context';
21
+ import { currentRenderCacheMeta } from '../cache/revalidate';
22
+ import {
23
+ afterRequestApisAllowed,
24
+ recordRequestPath,
25
+ runWithAfterRequestApiScope,
26
+ } from './after-scope';
27
+
28
+ type AfterTask<T> = Promise<T> | (() => T | Promise<T>);
29
+
30
+ const requestContextSymbol = Symbol.for('@next/request-context');
31
+
32
+ /**
33
+ * Marks an error that escaped an after() task during a BUILD prerender. The build's per-route
34
+ * skip-and-warn catch reads it to fail the build instead of silently degrading the route to dynamic -
35
+ * Next fails the build when after() throws while prerendering.
36
+ */
37
+ export const AFTER_PRERENDER_ERROR = Symbol.for('pnext.afterPrerenderError');
38
+
39
+ interface VercelRequestContext {
40
+ get?: () => { waitUntil?: (promise: Promise<unknown>) => void } | undefined;
41
+ }
42
+
43
+ /** The platform waitUntil, when a Vercel-style request context is present. */
44
+ function platformWaitUntil(): ((promise: Promise<unknown>) => void) | undefined {
45
+ const holder = (globalThis as Record<symbol, unknown>)[requestContextSymbol] as
46
+ | VercelRequestContext
47
+ | undefined;
48
+ return holder?.get?.()?.waitUntil;
49
+ }
50
+
51
+ /**
52
+ * Run a single after task, forwarding its settled promise to the platform
53
+ * waitUntil when available. Errors are logged with Next's exact `after()` prefix
54
+ * (phase-changes/cookies asserts the "An error occurred in a function passed to
55
+ * `after()`:" text) and never rethrown, so one failing task cannot break others.
56
+ */
57
+ async function runAfterTask<T>(task: AfterTask<T>): Promise<void> {
58
+ const promise = Promise.resolve()
59
+ .then(() => (typeof task === 'function' ? task() : task))
60
+ .then(
61
+ () => undefined,
62
+ error => {
63
+ console.error(
64
+ `An error occurred in a function passed to \`after()\`: ${
65
+ error instanceof Error ? (error.stack ?? `${error.name}: ${error.message}`) : String(error)
66
+ }`,
67
+ );
68
+ },
69
+ );
70
+ platformWaitUntil()?.(promise);
71
+ await promise;
72
+ }
73
+
74
+ /**
75
+ * Like runAfterTask but for build prerenders: the task's rejection propagates
76
+ * so a throwing after() fails the page's prerender (next-after-app-static),
77
+ * rather than being logged and swallowed. The settled promise is still forwarded
78
+ * to the platform waitUntil (with errors swallowed there to avoid an unhandled
79
+ * rejection) so a serverless platform keeps the invocation alive until it ends.
80
+ */
81
+ async function settleAfterTask<T>(task: AfterTask<T>): Promise<void> {
82
+ const promise = Promise.resolve().then(() =>
83
+ typeof task === 'function' ? task() : task,
84
+ );
85
+ platformWaitUntil()?.(promise.then(() => undefined, () => undefined));
86
+ try {
87
+ await promise;
88
+ } catch (error) {
89
+ // Tag so the build's per-route catch fails the build rather than degrading
90
+ // the route to dynamic. The tag rides the same error object up through the
91
+ // static after-drain (which re-throws it) to cli/build.ts.
92
+ if (error && typeof error === 'object') {
93
+ (error as Record<symbol, unknown>)[AFTER_PRERENDER_ERROR] = true;
94
+ }
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Run a prerender after() task in the 'after' phase, naming its route. The
101
+ * runtime flush sets the work unit to phase 'after' before draining; a build
102
+ * prerender drains through the render cache meta instead, so the phase + route
103
+ * are set here so request-API guards fire with Next's exact message (both
104
+ * no-op when there is no work unit, e.g. a route-handler prerender).
105
+ */
106
+ function runInPrerenderAfterPhase<T>(
107
+ route: string | undefined,
108
+ requestApisAllowed: boolean,
109
+ fn: () => T,
110
+ ): T {
111
+ setPhase('after');
112
+ if (route) recordRequestPath(route);
113
+ return runWithAfterRequestApiScope(requestApisAllowed, fn);
114
+ }
115
+
116
+ /**
117
+ * next/server after(): schedule a callback or promise to run once the response
118
+ * has been fully sent. Legal in dynamic pages, route handlers, server actions,
119
+ * middleware, and generateMetadata().
120
+ */
121
+ export function after<T>(task: AfterTask<T>): void {
122
+ // Capture the request scope at call time so a callback registered during a route handler or server
123
+ // action still resolves request APIs when it runs later in the flush. A page render does NOT capture:
124
+ // its after() callbacks run detached, so headers()/cookies()/connection() throw Next's "used inside
125
+ // after()" error. The phase distinguishes them - renders stay 'render', handlers set routeKind,
126
+ // actions set phase 'action'.
127
+ const unit = getWorkUnit();
128
+ const isHandlerOrAction =
129
+ unit?.phase === 'action' ||
130
+ unit?.phase === 'middleware' ||
131
+ unit?.routeKind === 'route-handler';
132
+ const isNestedRequestAfter =
133
+ unit?.phase === 'after' && Boolean(currentRequest()) && afterRequestApisAllowed();
134
+ // Handlers and actions capture the live request scope so headers()/cookies() keep working inside their
135
+ // after() callbacks. A page render (or generateMetadata) instead captures only the React cache()
136
+ // scope, so cache() shares the render's memo cache in the callback while the request scope stays
137
+ // detached and headers()/cookies()/connection() still throw Next's "used inside after()" error.
138
+ const withScope =
139
+ isHandlerOrAction || isNestedRequestAfter
140
+ ? captureRequestScope()
141
+ : captureCacheScope();
142
+ // Static-generation render (build prerender or ISR regeneration): there is
143
+ // no response close to flush against, so the task queues on the render's
144
+ // cache meta and the render drains it once produce settles (Next waits for
145
+ // after() during prerendering). Build prerenders propagate rejections so a
146
+ // throwing after() fails the page's prerender (next-after-app-static);
147
+ // runtime regenerations log-and-continue like every other runtime path.
148
+ const meta = currentRenderCacheMeta();
149
+ if (meta && (meta.prerender === true || meta.blockingStaleFetches === true)) {
150
+ // A build/ISR prerender runs with no work unit, so the top-level routeKind check above cannot tell a
151
+ // route-handler prerender from a page one - meta.handler does. A handler prerender captures the live
152
+ // request scope so request APIs resolve inside after(); a page prerender keeps only the cache scope
153
+ // so they throw "used inside after()".
154
+ const prerenderScope = meta.handler ? captureRequestScope() : withScope;
155
+ const route = meta.route;
156
+ const requestApisAllowed = Boolean(meta.handler);
157
+ const run = meta.prerender
158
+ ? () =>
159
+ prerenderScope(() =>
160
+ runInPrerenderAfterPhase(route, requestApisAllowed, () => settleAfterTask(task)),
161
+ )
162
+ : () => prerenderScope(() => runWithAfterRequestApiScope(requestApisAllowed, () => runAfterTask(task)));
163
+ (meta.afterTasks ??= []).push(run);
164
+ return;
165
+ }
166
+ const requestApisAllowed = Boolean(isHandlerOrAction || isNestedRequestAfter);
167
+ const queued = queueAfterTask(() =>
168
+ withScope(() => runWithAfterRequestApiScope(requestApisAllowed, () => runAfterTask(task))),
169
+ );
170
+ if (queued) return;
171
+ // No active work unit (e.g. called at module scope): best-effort microtask.
172
+ void withScope(() => runWithAfterRequestApiScope(requestApisAllowed, () => runAfterTask(task)));
173
+ }
@@ -0,0 +1,306 @@
1
+ // onRequestError funnel (COMPAT).
2
+ //
3
+ // Implements RequestExtensions.onRequestError: core calls it once per failed request with the error,
4
+ // request info and work-unit context. Compat classifies control-flow errors
5
+ // (notFound/redirect/forbidden/unauthorized/dynamic bailouts) and skips them, then forwards real errors
6
+ // to the user's instrumentation onRequestError(error, { path, method, headers }, context).
7
+ //
8
+ // Dedupe: the same request may surface an error at both the render site and the top-level catch, so a
9
+ // per-work-unit flag ensures the user hook fires exactly once.
10
+
11
+ import { getWorkUnit } from '../../request/context';
12
+ import { getRequestRuntime } from '../../routing/request-runtime';
13
+ import { selectRouteForRequest } from '../../routing/routes';
14
+ import path from 'node:path';
15
+ import type { RouteManifestEntry } from '../../types';
16
+ import type { RequestErrorContext, RequestErrorInfo } from '../../extensions';
17
+ import {
18
+ isForbiddenError,
19
+ isNotFoundError,
20
+ isRedirectError,
21
+ isUnauthorizedError,
22
+ } from '../../api/navigation';
23
+ import { isPostpone } from '../../ppr';
24
+ import { markErrorLogged } from '../../utils/error-log';
25
+ import { getUserOnRequestError, instrumentationReady } from './instrumentation';
26
+
27
+ const ERROR_REPORTED_KEY = Symbol.for('pnext.errorReported');
28
+
29
+ /** True for Next control-flow errors that must NOT be reported to the hook. */
30
+ function isControlFlowError(error: unknown): boolean {
31
+ return (
32
+ isRedirectError(error) ||
33
+ isNotFoundError(error) ||
34
+ isForbiddenError(error) ||
35
+ isUnauthorizedError(error) ||
36
+ isPostpone(error) ||
37
+ isNextDynamicNoSsr(error)
38
+ );
39
+ }
40
+
41
+ /** next/dynamic ssr:false bailout digest (skip-next-internal-error asserts skip). */
42
+ function isNextDynamicNoSsr(error: unknown): boolean {
43
+ const digest = (error as { digest?: unknown } | null | undefined)?.digest;
44
+ return (
45
+ digest === 'NEXT_DYNAMIC_NO_SSR_CODE' ||
46
+ (typeof digest === 'string' && digest.startsWith('DYNAMIC_SERVER_USAGE'))
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Convert a pnext route pattern (`/app-page/dynamic/:id`, `:slug*` catch-alls)
52
+ * into Next's display form (`/app-page/dynamic/[id]`, `[...slug]`), which the
53
+ * onRequestError `routePath` field reports. Mirrors pnext-cli's toNextRoute.
54
+ */
55
+ function toDisplayRoute(route: RouteManifestEntry): string {
56
+ let value = route.route || '/';
57
+ if (route.catchAll) {
58
+ const pattern = route.catchAllOptional
59
+ ? `[[...${route.catchAll}]]`
60
+ : `[...${route.catchAll}]`;
61
+ value = value.replace(`:${route.catchAll}*`, pattern);
62
+ }
63
+ for (const param of route.params) value = value.replace(`:${param}`, `[${param}]`);
64
+ return value;
65
+ }
66
+
67
+ /** Return `Pages Router` when route files live under pages/, otherwise `App Router`. */
68
+ function routeKindFromFile(
69
+ runtimeRoot: string | undefined,
70
+ route: RouteManifestEntry,
71
+ ): 'Pages Router' | 'App Router' {
72
+ if (!runtimeRoot) {
73
+ return 'App Router';
74
+ }
75
+ const normalized = path.normalize(route.file);
76
+ const pagesRoot = path.join(runtimeRoot, 'pages');
77
+ const appRoot = path.join(runtimeRoot, 'app');
78
+ return normalized.startsWith(pagesRoot + path.sep) || normalized === pagesRoot
79
+ ? 'Pages Router'
80
+ : normalized.startsWith(appRoot + path.sep) || normalized === appRoot
81
+ ? 'App Router'
82
+ : 'App Router';
83
+ }
84
+
85
+ interface RouteMatch {
86
+ route: RouteManifestEntry;
87
+ routePath: string;
88
+ }
89
+
90
+ function resolveRoute(pathname: string): RouteMatch | undefined {
91
+ const runtime = getRequestRuntime();
92
+ if (!runtime) return undefined;
93
+ const selectedPath = pathname.split('?')[0] ?? pathname;
94
+ const matched = selectRouteForRequest(runtime.routes, selectedPath, undefined);
95
+ if (!matched) return undefined;
96
+ return { route: matched.route, routePath: toDisplayRoute(matched.route) };
97
+ }
98
+
99
+ /**
100
+ * Resolve the matched route's display pattern for the failing request from the
101
+ * live routing runtime (the same seam otel uses for `next.route`). Returns
102
+ * undefined outside a request scope or when no route matches (e.g. a 404).
103
+ */
104
+ function resolveRoutePath(path: string): string | undefined {
105
+ try {
106
+ return resolveRoute(path)?.routePath;
107
+ } catch {
108
+ return undefined;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Map the work-unit facts onto Next's onRequestError context. pnext is
114
+ * app-router only, so routerKind is always 'App Router'. routeType is 'route'
115
+ * for route handlers and 'render' for page renders. routePath is the matched
116
+ * route's display pattern. renderSource is reported for page renders (route
117
+ * handlers leave it undefined). A dedicated revalidateReason is threaded in when
118
+ * core reports an ISR revalidation error.
119
+ */
120
+ function errorDigest(error: unknown): string | undefined {
121
+ const digest = (error as { digest?: unknown } | null | undefined)?.digest;
122
+ return typeof digest === 'string' ? digest : undefined;
123
+ }
124
+
125
+ function buildContext(
126
+ error: unknown,
127
+ context: RequestErrorContext,
128
+ path: string,
129
+ ): Record<string, unknown> {
130
+ const runtime = getRequestRuntime();
131
+ const match = runtime ? resolveRoute(path) : undefined;
132
+ const out: Record<string, unknown> = {};
133
+ out.routerKind = match
134
+ ? routeKindFromFile(runtime?.config.root, match.route)
135
+ : 'App Router';
136
+ out.routeType = context.routeKind === 'route-handler' ? 'route' : 'render';
137
+ const routePath = match ? match.routePath : resolveRoutePath(path);
138
+ if (routePath) out.routePath = routePath;
139
+ if (context.routeKind === 'html') {
140
+ out.renderSource =
141
+ (context as { renderSource?: string }).renderSource ?? 'react-server-components';
142
+ }
143
+ const revalidateReason = (context as { revalidateReason?: string }).revalidateReason;
144
+ if (revalidateReason) out.revalidateReason = revalidateReason;
145
+ const digest = errorDigest(error);
146
+ if (digest) out.digest = digest;
147
+ return out;
148
+ }
149
+
150
+ /**
151
+ * Next's onRequestError context for a thrown server action. Shared by the action endpoint path and the
152
+ * render-funnel path: an inline `<form action>` action throws DURING the page re-render, so core's
153
+ * render funnel reports it first and wins the per-work-unit dedupe - it must carry the same action
154
+ * context the endpoint would (routeType 'action', RSC-payload source) or the suite sees a render context.
155
+ */
156
+ function buildActionContext(error: unknown, path: string): Record<string, unknown> {
157
+ const runtime = getRequestRuntime();
158
+ const match = runtime ? resolveRoute(path) : undefined;
159
+ const out: Record<string, unknown> = {
160
+ routerKind: match ? routeKindFromFile(runtime?.config.root, match.route) : 'App Router',
161
+ routeType: 'action',
162
+ renderSource: 'react-server-components-payload',
163
+ };
164
+ const routePath = match ? match.routePath : resolveRoutePath(path);
165
+ if (routePath) out.routePath = routePath;
166
+ const digest = errorDigest(error);
167
+ if (digest) out.digest = digest;
168
+ return out;
169
+ }
170
+
171
+ /** True when the failing request is a server-action invocation (JS/form dispatch). */
172
+ function isServerActionRequest(headers: Headers): boolean {
173
+ if (headers.has('next-action')) return true;
174
+ const accept = headers.get('accept') ?? '';
175
+ return accept.includes('text/x-component');
176
+ }
177
+
178
+ /**
179
+ * Report a thrown server-action error to the user's onRequestError hook. The
180
+ * action endpoint swallows the throw into a masked 500 response, so core's funnel
181
+ * never sees it; this is the action-path equivalent. Context mirrors Next's
182
+ * action error shape (`routeType: 'action'`, RSC-payload render source) the
183
+ * on-request-error/server-action-error suite asserts. Skips control-flow errors
184
+ * (redirect()/notFound()) and never throws.
185
+ */
186
+ export async function reportActionErrorToUser(error: unknown, request: Request): Promise<void> {
187
+ if (isControlFlowError(error)) return;
188
+
189
+ const unit = getWorkUnit();
190
+ if (unit) {
191
+ const bag = (unit.compat ??= {});
192
+ if (bag[ERROR_REPORTED_KEY]) return;
193
+ bag[ERROR_REPORTED_KEY] = true;
194
+ }
195
+
196
+ await instrumentationReady();
197
+ const hook = getUserOnRequestError();
198
+ if (!hook) return;
199
+
200
+ let path = request.url;
201
+ try {
202
+ const parsed = new URL(request.url);
203
+ path = parsed.pathname + parsed.search;
204
+ } catch {
205
+ // request.url is already a path.
206
+ }
207
+
208
+ try {
209
+ await hook(
210
+ error,
211
+ { path, method: request.method, headers: headersToObject(request.headers) },
212
+ buildActionContext(error, path),
213
+ );
214
+ } catch (hookError) {
215
+ console.error('instrumentation onRequestError failed:', hookError);
216
+ }
217
+ }
218
+
219
+ function headersToObject(headers: Headers): Record<string, string> {
220
+ const out: Record<string, string> = {};
221
+ headers.forEach((value, key) => {
222
+ out[key] = value;
223
+ });
224
+ return out;
225
+ }
226
+
227
+ /**
228
+ * RequestExtensions.onRequestError implementation. Skips control-flow errors,
229
+ * dedupes per work unit, and forwards to the user hook. Never throws.
230
+ */
231
+ export async function reportRequestErrorToUser(
232
+ error: unknown,
233
+ requestInfo: RequestErrorInfo,
234
+ context: RequestErrorContext,
235
+ ): Promise<void> {
236
+ if (isControlFlowError(error)) return;
237
+
238
+ const unit = getWorkUnit();
239
+ if (unit) {
240
+ const bag = (unit.compat ??= {});
241
+ if (bag[ERROR_REPORTED_KEY]) return;
242
+ bag[ERROR_REPORTED_KEY] = true;
243
+ }
244
+
245
+ await instrumentationReady();
246
+ const hook = getUserOnRequestError();
247
+ if (!hook) return;
248
+
249
+ // `path` is the original URL incl. search (dynamic-routes asserts search present).
250
+ let path = requestInfo.url;
251
+ try {
252
+ const parsed = new URL(requestInfo.url);
253
+ path = parsed.pathname + parsed.search;
254
+ } catch {
255
+ // requestInfo.url is already a path.
256
+ }
257
+
258
+ // An inline `<form action>` action throws inside the page re-render, so this
259
+ // render funnel reports before the action endpoint and wins the dedupe — emit
260
+ // the action context so it matches what the endpoint would have reported.
261
+ const hookContext = isServerActionRequest(requestInfo.headers)
262
+ ? buildActionContext(error, path)
263
+ : buildContext(error, context, path);
264
+
265
+ try {
266
+ await hook(
267
+ error,
268
+ { path, method: requestInfo.method, headers: headersToObject(requestInfo.headers) },
269
+ hookContext,
270
+ );
271
+ } catch (hookError) {
272
+ console.error('instrumentation onRequestError failed:', hookError);
273
+ }
274
+ }
275
+
276
+ let guardInstalled = false;
277
+
278
+ /**
279
+ * Server-lifecycle safety net for SSR errors that escape as unhandled promise rejections instead of
280
+ * surfacing through a render catch.
281
+ *
282
+ * A `'use client'` component that throws during server rendering is stringified by
283
+ * preact-render-to-string's async pass, which RESOLVES the render to an empty string while floating the
284
+ * component's own rejected promise. That rejection is never awaited by the render pipeline, so without
285
+ * this guard it reaches the process as an unhandledRejection and takes down every in-flight and
286
+ * subsequent request.
287
+ *
288
+ * Installed once, compat-only:
289
+ * - Control-flow signals are navigation intents, not failures. They are absorbed silently, never
290
+ * logged, matching Next's skip-next-internal-error contract - the raw digests would otherwise print
291
+ * the internal strings those tests assert are absent.
292
+ * - A real error is logged once, deduped against the render/handler catch's own log, so it is never
293
+ * silently lost. The listener's mere presence stops the default process crash.
294
+ *
295
+ * The correct request-scoped report for such an error requires the renderer to catch the async client
296
+ * throw at the SSR site and route it through reportRequestError; this net only guarantees survival.
297
+ */
298
+ export function installUnhandledRejectionGuard(): void {
299
+ if (guardInstalled) return;
300
+ if (typeof process === 'undefined' || typeof process.on !== 'function') return;
301
+ guardInstalled = true;
302
+ process.on('unhandledRejection', (reason: unknown) => {
303
+ if (isControlFlowError(reason)) return;
304
+ if (markErrorLogged(reason)) console.error(reason);
305
+ });
306
+ }
@@ -0,0 +1,87 @@
1
+ // Error serialization for boundaries (COMPAT).
2
+ //
3
+ // Implements RenderExtensions.serializeError: Next's digest protocol plus prod message redaction for
4
+ // the `{ name, message, digest }` object handed to an error.js / global-error.js boundary.
5
+ //
6
+ // - A user-supplied `.digest` string passes through unchanged.
7
+ // - A real server error without a digest gets a stable hash digest.
8
+ // - In production the message is redacted to React's minified #441 text; in dev the original message
9
+ // survives (non-Error throws arrive as 'undefined' / 'null' upstream).
10
+ //
11
+ // NOTE: this function must NOT log the error to the server console. Core's renderer already logs the
12
+ // render error exactly once right before it calls serializeError(); logging here too double-prints it.
13
+
14
+ import type { SerializeErrorContext, SerializedError } from '../../extensions';
15
+ import { isTaggedPrimitiveThrow, tagPrimitiveThrow, untagPrimitiveThrow } from '../client/errors/primitive-throw';
16
+
17
+ // React's production redaction text for RSC render errors (#441). Boundary
18
+ // suites assert this verbatim in prod.
19
+ const REDACTED_MESSAGE =
20
+ 'Minified React error #441; visit https://react.dev/errors/441 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.';
21
+
22
+ // Next uses next/dist/compiled/string-hash; any stable non-negative hash whose
23
+ // decimal string matches /\w+/ satisfies the compat assertions.
24
+ function stableDigest(input: string): string {
25
+ let hash = 5381;
26
+ for (let i = 0; i < input.length; i++) {
27
+ hash = (hash * 33) ^ input.charCodeAt(i);
28
+ }
29
+ return (hash >>> 0).toString();
30
+ }
31
+
32
+ function customDigest(error: Error): string | undefined {
33
+ const digest = (error as { digest?: unknown }).digest;
34
+ return typeof digest === 'string' && digest.length > 0 ? digest : undefined;
35
+ }
36
+
37
+ // Next appends an error-code suffix to the hash digest when the thrown value
38
+ // was not an Error: prod RSC wraps a nullish throw and reports `@E394`
39
+ // (test/e2e/app-dir/errors asserts `digest: '<hash>@E394'` in the prod log).
40
+ function digestSuffix(error: Error): string {
41
+ if (!isTaggedPrimitiveThrow(error)) return '';
42
+ const raw = untagPrimitiveThrow(error);
43
+ return raw === null || raw === undefined ? '@E394' : '';
44
+ }
45
+
46
+ /**
47
+ * RenderExtensions.wrapThrownValue: wrap a non-Error server throw the way Next's RSC transport does -
48
+ * `Error: <String(value)>` with the synthetic `at stringify (<anonymous>)` stack frame the prod log
49
+ * snapshot asserts, and the raw value tagged so the digest picks up the nullish suffix.
50
+ */
51
+ export function wrapThrownValue(value: unknown): Error {
52
+ const error = tagPrimitiveThrow(value);
53
+ error.stack = `Error: ${error.message}\n at stringify (<anonymous>)`;
54
+ return error;
55
+ }
56
+
57
+ /**
58
+ * RenderExtensions.formatErrorLog: Next's prod server-log shape for a render error - the error's
59
+ * inspected form with the digest as an own property, asserted byte-exact by the errors suite's inline
60
+ * snapshots.
61
+ */
62
+ export function formatErrorLog(trace: string, digest: string, error?: Error): string {
63
+ // An error funneled out of a 'use cache' scope carries Next's environment tag;
64
+ // util.inspect prints it before the digest, so the log must too.
65
+ const environmentName = (error as { environmentName?: unknown } | undefined)?.environmentName;
66
+ const environment =
67
+ typeof environmentName === 'string' ? ` environmentName: '${environmentName}',\n` : '';
68
+ return `⨯ ${trace} {\n${environment} digest: '${digest}'\n}`;
69
+ }
70
+
71
+ export function serializeBoundaryError(ctx: SerializeErrorContext): SerializedError {
72
+ const { error, dev } = ctx;
73
+ const custom = customDigest(error);
74
+ const digest =
75
+ custom ?? stableDigest(`${error.message}${error.stack ?? ''}`) + digestSuffix(error);
76
+
77
+ // Boundary components render `${error}` (e.g. `Global error: ${error}`), so
78
+ // the prop must stringify as `Error: <message>` like React's transported
79
+ // error. Return a real Error carrying the (possibly redacted) message + digest.
80
+ const boundaryError = new Error(dev ? error.message : REDACTED_MESSAGE) as Error & {
81
+ digest?: string;
82
+ };
83
+ boundaryError.name = error.name || 'Error';
84
+ boundaryError.digest = digest;
85
+ boundaryError.stack = undefined;
86
+ return boundaryError;
87
+ }