@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,944 @@
1
+ // OpenTelemetry span emission (COMPAT).
2
+ //
3
+ // Next's span taxonomy (~12 span types with exact next.span_type / next.span_name / attribute shapes).
4
+ // The root `BaseServer.handleRequest` span is opened in a request interceptor, its OTel Context stashed
5
+ // on the request WorkUnit, and closed in a responseFinalizer once the status is known. Child spans
6
+ // re-enter the stashed root context so they nest under the root - pnext threads parenting through its
7
+ // OWN work-unit ALS rather than relying on the app's ContextManager spanning our async boundaries.
8
+ //
9
+ // Everything is inert unless @opentelemetry/api resolves from the app AND a work unit is active.
10
+
11
+ import { getWorkUnit, queueAfterTask, type WorkUnit } from '../../request/context';
12
+ import { getOtelApi, type OtelApi, type OtelContext, type OtelSpan } from './api';
13
+
14
+ // next.span_type taxonomy (exact strings Next emits; asserted verbatim).
15
+ export const SPAN_TYPE = {
16
+ handleRequest: 'BaseServer.handleRequest',
17
+ getBodyResult: 'AppRender.getBodyResult',
18
+ fetch: 'AppRender.fetch',
19
+ runHandler: 'AppRouteRouteHandlers.runHandler',
20
+ generateMetadata: 'ResolveMetadata.generateMetadata',
21
+ createComponentTree: 'NextNodeServer.createComponentTree',
22
+ getLayoutOrPageModule: 'NextNodeServer.getLayoutOrPageModule',
23
+ findPageComponents: 'NextNodeServer.findPageComponents',
24
+ startResponse: 'NextNodeServer.startResponse',
25
+ clientComponentLoading: 'NextNodeServer.clientComponentLoading',
26
+ middleware: 'Middleware.execute',
27
+ getRequestHandler: 'NextServer.getRequestHandler',
28
+ nodeRunHandler: 'Node.runHandler',
29
+ renderDocument: 'Render.renderDocument',
30
+ getServerSideProps: 'Render.getServerSideProps',
31
+ getStaticProps: 'Render.getStaticProps',
32
+ } as const;
33
+
34
+ // The runtime label the app's exporter reads at export time. Next's test exporter serializes
35
+ // `process.env.NEXT_RUNTIME` per span; for a route declared `runtime: 'edge'` Next runs the render in
36
+ // the edge sandbox, so its spans say 'edge' while node-server-side spans stay 'nodejs'. pnext runs
37
+ // everything in one Bun process, so the tracer mirrors the per-span runtime split by setting
38
+ // NEXT_RUNTIME around each span.end() - SimpleSpanProcessor exports synchronously inside end().
39
+ type SpanRuntime = 'edge' | 'nodejs';
40
+
41
+ // Span types Next emits from INSIDE the edge sandbox when the route is edge.
42
+ const EDGE_SANDBOX_SPAN_TYPES: ReadonlySet<string> = new Set([
43
+ SPAN_TYPE.getBodyResult,
44
+ SPAN_TYPE.createComponentTree,
45
+ SPAN_TYPE.getLayoutOrPageModule,
46
+ SPAN_TYPE.generateMetadata,
47
+ SPAN_TYPE.fetch,
48
+ SPAN_TYPE.renderDocument,
49
+ SPAN_TYPE.getServerSideProps,
50
+ SPAN_TYPE.getStaticProps,
51
+ ]);
52
+
53
+ // Per-WorkUnit otel state (stashed on the compat scratch map).
54
+ const OTEL_STATE = Symbol.for('pnext.compat.otel.state');
55
+
56
+ interface OtelState {
57
+ api: OtelApi;
58
+ /** Context with the root span active — children re-enter this to nest. */
59
+ rootContext: OtelContext;
60
+ /** The root BaseServer.handleRequest span (closed by the finalizer). */
61
+ rootSpan: OtelSpan;
62
+ /** Guard so the root span is ended exactly once. */
63
+ rootEnded: boolean;
64
+ /** Request method, kept for the pattern-rename span name. */
65
+ method: string;
66
+ /** Whether this was an RSC request, kept for the pattern-rename span name. */
67
+ rsc: boolean;
68
+ /** The raw request target (`pathname + search`) the root span opened with. */
69
+ target: string;
70
+ /**
71
+ * The extracted INCOMING context (traceparent applied, no pnext span active). Top-level spans parent
72
+ * here, mirroring Next's spans that sit beside - not under - handleRequest.
73
+ */
74
+ incomingContext: OtelContext;
75
+ /**
76
+ * Edge emulation mode for the matched route. 'page': the whole render tree
77
+ * exports as runtime 'edge' with the params-query target (Next renders edge
78
+ * pages inside the sandbox). 'handler': only the runHandler span is 'edge'
79
+ * and it parents on the incoming context (Next's node server keeps its own
80
+ * nodejs spans; the sandbox emits a stand-alone runHandler).
81
+ */
82
+ edgeMode?: 'page' | 'handler';
83
+ /** NEXT_OTEL_VERBOSE=1 wrapper span (NextServer.getRequestHandler). */
84
+ verboseSpan?: OtelSpan;
85
+ /**
86
+ * The context a NEW child span parents under. Starts as rootContext; each
87
+ * withChildSpan pushes its own child context for the duration of `fn` so a
88
+ * nested withChildSpan (e.g. `resolve segment modules` inside `build component
89
+ * tree`) nests under its lexical parent, not the root. Restored on settle.
90
+ */
91
+ currentContext: OtelContext;
92
+ /** The stamped display route pattern (`/pages/[param]/getServerSideProps`). */
93
+ route?: string;
94
+ /**
95
+ * A pages-router data fetcher errored or returned notFound and the fallback span set replaced the
96
+ * render span. Post-fallback render-internal leaves must not surface as root children - Next does not
97
+ * emit them there.
98
+ */
99
+ pagesFallback?: boolean;
100
+ /** A materialized pages-API handler ran (suppresses the start-response leaf). */
101
+ pagesApi?: boolean;
102
+ renderSpan?: {
103
+ span: OtelSpan;
104
+ parentContext: OtelContext;
105
+ spanContext: OtelContext;
106
+ ended: boolean;
107
+ /** markRenderSpanError already set an ERROR status; finish must keep it. */
108
+ errored?: boolean;
109
+ /** `next.span_type` the span opened with (runtime label at end). */
110
+ spanType: string;
111
+ /**
112
+ * Pages-router notFound/error fallback: Next replaces the route's render
113
+ * span with the fallback page's own spans (`/_error`, `/_not-found`). A
114
+ * discarded span is NEVER ended, so SimpleSpanProcessor never exports it
115
+ * (its children become orphans the suite's tree-builder filters out).
116
+ */
117
+ discarded?: boolean;
118
+ };
119
+ componentTreeSpan?: {
120
+ span: OtelSpan;
121
+ parentContext: OtelContext;
122
+ spanContext: OtelContext;
123
+ ended: boolean;
124
+ };
125
+ }
126
+
127
+ /** The runtime label for a child span of the given next.span_type. */
128
+ function childRuntime(state: OtelState, spanType: string): SpanRuntime {
129
+ return state.edgeMode === 'page' && EDGE_SANDBOX_SPAN_TYPES.has(spanType) ? 'edge' : 'nodejs';
130
+ }
131
+
132
+ let cachedAppRoot: string | undefined;
133
+ let spanEndDepth = 0;
134
+
135
+ /** Called once at init so the tracer can resolve @opentelemetry/api from the app. */
136
+ export function setOtelAppRoot(root: string): void {
137
+ cachedAppRoot = root;
138
+ }
139
+
140
+ function api(): OtelApi | null {
141
+ return cachedAppRoot ? getOtelApi(cachedAppRoot) : null;
142
+ }
143
+
144
+ function stateOf(unit: WorkUnit | undefined): OtelState | undefined {
145
+ return unit?.compat?.[OTEL_STATE] as OtelState | undefined;
146
+ }
147
+
148
+ /**
149
+ * Open the root `BaseServer.handleRequest` span for the active request and stash
150
+ * it (+ its active context) on the work unit. Extracts an incoming `traceparent`
151
+ * so the root span inherits the caller's trace/parent. No-op when otel-api is
152
+ * unavailable, no work unit is active, or a root is already open.
153
+ */
154
+ export function startRootSpan(input: {
155
+ method: string;
156
+ target: string;
157
+ rsc: boolean;
158
+ headers: Headers;
159
+ }): void {
160
+ const a = api();
161
+ if (!a) return;
162
+ const unit = getWorkUnit();
163
+ if (!unit) return;
164
+ unit.compat ??= {};
165
+ if (unit.compat[OTEL_STATE]) return;
166
+
167
+ // Extract incoming context (traceparent + any custom propagators, e.g. the
168
+ // test's x-custom sampler entry). The carrier is a plain header record.
169
+ const carrier: Record<string, string> = {};
170
+ input.headers.forEach((value, key) => {
171
+ carrier[key] = value;
172
+ });
173
+ const incomingContext = a.propagation.extract(a.context.active(), carrier, headerGetter);
174
+
175
+ const spanName = `${input.rsc ? 'RSC ' : ''}${input.method} ${input.target}`;
176
+ const tracer = a.trace.getTracer('next.js', '0.0.1');
177
+ // NEXT_OTEL_VERBOSE=1: Next additionally emits the NextServer.getRequestHandler
178
+ // wrapper span between the incoming context and handleRequest. The verbose
179
+ // suite asserts its span_type + that it parents directly on the incoming
180
+ // traceparent while everything else nests below.
181
+ let verboseSpan: OtelSpan | undefined;
182
+ let rootParentCtx = incomingContext;
183
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
184
+ if (process.env.NEXT_OTEL_VERBOSE === '1') {
185
+ verboseSpan = tracer.startSpan(
186
+ SPAN_TYPE.getRequestHandler,
187
+ {
188
+ kind: a.SpanKind.INTERNAL,
189
+ attributes: {
190
+ 'next.span_name': SPAN_TYPE.getRequestHandler,
191
+ 'next.span_type': SPAN_TYPE.getRequestHandler,
192
+ },
193
+ },
194
+ incomingContext,
195
+ );
196
+ rootParentCtx = a.trace.setSpan(incomingContext, verboseSpan);
197
+ }
198
+ const rootSpan = tracer.startSpan(
199
+ spanName,
200
+ {
201
+ kind: a.SpanKind.SERVER,
202
+ attributes: {
203
+ 'http.method': input.method,
204
+ 'http.target': input.target,
205
+ 'next.rsc': input.rsc,
206
+ 'next.span_name': spanName,
207
+ 'next.span_type': SPAN_TYPE.handleRequest,
208
+ },
209
+ },
210
+ rootParentCtx,
211
+ );
212
+ const rootContext = a.trace.setSpan(rootParentCtx, rootSpan);
213
+ const state: OtelState = {
214
+ api: a,
215
+ rootContext,
216
+ rootSpan,
217
+ rootEnded: false,
218
+ method: input.method,
219
+ rsc: input.rsc,
220
+ target: input.target,
221
+ incomingContext,
222
+ ...(verboseSpan ? { verboseSpan } : {}),
223
+ currentContext: rootContext,
224
+ };
225
+ unit.compat[OTEL_STATE] = state;
226
+ }
227
+
228
+ /**
229
+ * Set the route name on the root span once the route is matched. `next.route` /
230
+ * `http.route` are the route PATTERN (`/app/[param]/rsc-fetch`); the span name is
231
+ * `[RSC ]<METHOD> <route>` (Next renames from the raw target to the pattern).
232
+ */
233
+ export function setRootSpanRoute(
234
+ route: string,
235
+ options: {
236
+ /** The matched route declared `runtime: 'edge'`. */
237
+ edge?: boolean;
238
+ /** 'handler' for a route handler, 'page' otherwise. */
239
+ kind?: 'page' | 'handler';
240
+ /** Resolved dynamic params (edge target emulation). */
241
+ params?: Record<string, string | string[]>;
242
+ } = {},
243
+ ): void {
244
+ const state = stateOf(getWorkUnit());
245
+ if (!state) return;
246
+ state.route = route;
247
+ const { rootSpan } = state;
248
+ const name = `${state.rsc ? 'RSC ' : ''}${state.method} ${route}`;
249
+ rootSpan.setAttribute('next.route', route);
250
+ rootSpan.setAttribute('http.route', route);
251
+ rootSpan.setAttribute('next.span_name', name);
252
+ rootSpan.updateName(name);
253
+ if (!options.edge) return;
254
+ state.edgeMode = options.kind === 'handler' ? 'handler' : 'page';
255
+ if (state.edgeMode !== 'page') return;
256
+ // A production start invokes an edge page through the sandbox with the resolved dynamic params
257
+ // appended as query params, so the sandbox-side handleRequest span reports them in its route. Mirror
258
+ // that. Always rebuilt from the ORIGINAL request target - the route is stamped twice (early
259
+ // pre-rewrite and late post-rewrite interceptors) and the params must not accumulate.
260
+ const [pathname = '', search = ''] = splitTarget(state.target);
261
+ const query = new URLSearchParams(search);
262
+ for (const [key, value] of Object.entries(options.params ?? {})) {
263
+ if (Array.isArray(value)) query.set(key, value.join('/'));
264
+ else query.set(key, value);
265
+ }
266
+ const queryText = query.toString();
267
+ if (queryText) {
268
+ rootSpan.setAttribute('http.target', `${pathname}?${queryText}`);
269
+ }
270
+ }
271
+
272
+ function splitTarget(target: string): [string, string] {
273
+ const index = target.indexOf('?');
274
+ return index === -1 ? [target, ''] : [target.slice(0, index), target.slice(index + 1)];
275
+ }
276
+
277
+ /**
278
+ * Close the root span with the final HTTP status. `code:2` (ERROR) + `error.type`
279
+ * for >=500 (synchronous failures); streaming errors keep status 200 and are
280
+ * marked on the render span instead. Idempotent.
281
+ */
282
+ export function endRootSpan(status: number): void {
283
+ const state = stateOf(getWorkUnit());
284
+ if (!state || state.rootEnded) return;
285
+ state.rootEnded = true;
286
+ const { api: a, rootSpan } = state;
287
+ rootSpan.setAttribute('http.status_code', status);
288
+ if (status >= 500) {
289
+ rootSpan.setAttribute('error.type', String(status));
290
+ rootSpan.setStatus({ code: a.SpanStatusCode.ERROR });
291
+ } else {
292
+ rootSpan.setStatus({ code: a.SpanStatusCode.UNSET });
293
+ }
294
+ endSpan(rootSpan, state.edgeMode === 'page' ? 'edge' : 'nodejs');
295
+ if (state.verboseSpan) {
296
+ state.verboseSpan.setStatus({ code: a.SpanStatusCode.UNSET });
297
+ endSpan(state.verboseSpan, 'nodejs');
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Fallback close for the error path: core's synchronous-500 catch returns a bare Response WITHOUT
303
+ * running responseFinalizers, so endRootSpan(status) never fires there. The error funnel, which DOES run
304
+ * on that path, calls this to end the root span with the fallback status. Idempotent: on the streaming
305
+ * path the finalizer already ended the span, so this no-ops - the funnel must NOT downgrade an
306
+ * already-ended 200 span to 500.
307
+ */
308
+ export function endRootSpanIfUnclosed(fallbackStatus: number): void {
309
+ const state = stateOf(getWorkUnit());
310
+ if (!state || state.rootEnded) return;
311
+ endRootSpan(fallbackStatus);
312
+ }
313
+
314
+ /** The active root span's spanContext. */
315
+ export function rootSpanContext(): { traceId: string; spanId: string } | undefined {
316
+ const state = stateOf(getWorkUnit());
317
+ if (!state) return undefined;
318
+ const sc = state.rootSpan.spanContext();
319
+ return { traceId: sc.traceId, spanId: sc.spanId };
320
+ }
321
+
322
+ /** The stashed root OTel context (root span active). */
323
+ export function rootOtelContext(): { api: OtelApi; context: OtelContext } | undefined {
324
+ const state = stateOf(getWorkUnit());
325
+ if (!state) return undefined;
326
+ return { api: state.api, context: state.rootContext };
327
+ }
328
+
329
+ /** The active render context, for client trace propagation. */
330
+ export function activeOtelContext(): { api: OtelApi; context: OtelContext } | undefined {
331
+ const state = stateOf(getWorkUnit());
332
+ if (!state) return undefined;
333
+ return { api: state.api, context: state.api.context.active() };
334
+ }
335
+
336
+ /**
337
+ * Run `fn` inside a child span nested under the request's root span. The child becomes the active parent
338
+ * for any spans `fn` opens. Returns `fn`'s result. When otel is inert or no root is open, runs `fn`
339
+ * untouched. `onError` lets callers mark render-body (streaming) errors with a message without failing
340
+ * the request.
341
+ */
342
+ export function withChildSpan<T>(
343
+ name: string,
344
+ spanType: string,
345
+ attributes: Record<string, unknown>,
346
+ fn: () => T,
347
+ options: { kind?: number; onError?: (span: OtelSpan, error: unknown) => void } = {},
348
+ ): T {
349
+ const state = stateOf(getWorkUnit());
350
+ if (!state) return fn();
351
+ const { api: a } = state;
352
+ // Parent under the lexically-enclosing span (currentContext), not always the
353
+ // root, so nested withChildSpan calls form the right tree.
354
+ const parentContext = state.currentContext;
355
+ const tracer = a.trace.getTracer('next.js', '0.0.1');
356
+ const span = tracer.startSpan(
357
+ name,
358
+ {
359
+ kind: options.kind ?? a.SpanKind.INTERNAL,
360
+ attributes: {
361
+ ...attributes,
362
+ 'next.span_name': name,
363
+ 'next.span_type': spanType,
364
+ },
365
+ },
366
+ parentContext,
367
+ );
368
+ const childContext = a.trace.setSpan(parentContext, span);
369
+ state.currentContext = childContext;
370
+ const settle = (error?: unknown) => {
371
+ // Restore the enclosing context so siblings parent correctly.
372
+ state.currentContext = parentContext;
373
+ if (error !== undefined) {
374
+ if (options.onError) options.onError(span, error);
375
+ else {
376
+ span.recordException(error);
377
+ span.setStatus({ code: a.SpanStatusCode.ERROR, message: errorMessage(error) });
378
+ }
379
+ } else {
380
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
381
+ }
382
+ endSpan(span, childRuntime(state, spanType));
383
+ };
384
+ try {
385
+ const result = a.context.with(childContext, fn);
386
+ if (isThenable(result)) {
387
+ return (result as PromiseLike<unknown>).then(
388
+ value => {
389
+ settle();
390
+ return value;
391
+ },
392
+ error => {
393
+ settle(error);
394
+ throw error;
395
+ },
396
+ ) as T;
397
+ }
398
+ settle();
399
+ return result;
400
+ } catch (error) {
401
+ settle(error);
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ export function withRenderBodySpan<T>(
407
+ name: string,
408
+ route: string,
409
+ fn: () => T,
410
+ spanType: string = SPAN_TYPE.getBodyResult,
411
+ ): T {
412
+ const state = stateOf(getWorkUnit());
413
+ if (!state) return fn();
414
+ const { api: a } = state;
415
+ const parentContext = state.currentContext;
416
+ const tracer = a.trace.getTracer('next.js', '0.0.1');
417
+ const span = tracer.startSpan(
418
+ name,
419
+ {
420
+ kind: a.SpanKind.INTERNAL,
421
+ attributes: {
422
+ 'next.route': route,
423
+ 'next.span_name': name,
424
+ 'next.span_type': spanType,
425
+ },
426
+ },
427
+ parentContext,
428
+ );
429
+ const spanContext = a.trace.setSpan(parentContext, span);
430
+ const previousRenderSpan = state.renderSpan;
431
+ state.renderSpan = { span, parentContext, spanContext, ended: false, spanType };
432
+ state.currentContext = spanContext;
433
+
434
+ const finish = (error?: unknown) => {
435
+ const current = state.renderSpan;
436
+ if (current?.span !== span || current.ended) return;
437
+ current.ended = true;
438
+ endComponentTreeSpan(state);
439
+ if (current.discarded) {
440
+ // Pages fallback replaced this span — leave it un-ended (never exported).
441
+ state.renderSpan = previousRenderSpan;
442
+ if (state.currentContext === spanContext) state.currentContext = parentContext;
443
+ return;
444
+ }
445
+ if (error !== undefined) markSpanError(a, span, error);
446
+ // A streaming (Suspense-boundary) error already stamped ERROR through
447
+ // markRenderSpanError — never downgrade it back to UNSET here.
448
+ else if (!current.errored && span.isRecording()) {
449
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
450
+ }
451
+ endSpan(span, childRuntime(state, spanType));
452
+ state.renderSpan = previousRenderSpan;
453
+ if (state.currentContext === spanContext) state.currentContext = parentContext;
454
+ };
455
+
456
+ const queued = queueAfterTask(() => finish());
457
+ try {
458
+ const result = a.context.with(spanContext, fn);
459
+ if (isThenable(result)) {
460
+ return (result as PromiseLike<unknown>).then(
461
+ value => {
462
+ if (!queued) finish();
463
+ return value;
464
+ },
465
+ error => {
466
+ finish(error);
467
+ throw error;
468
+ },
469
+ ) as T;
470
+ }
471
+ if (!queued) finish();
472
+ return result;
473
+ } catch (error) {
474
+ finish(error);
475
+ throw error;
476
+ }
477
+ }
478
+
479
+ export function markRenderSpanError(error: unknown): void {
480
+ const state = stateOf(getWorkUnit());
481
+ const render = state?.renderSpan;
482
+ if (!state || !render || render.ended) return;
483
+ render.errored = true;
484
+ markSpanError(state.api, render.span, error);
485
+ }
486
+
487
+ /**
488
+ * Run a route-handler invocation inside the `executing api route (app) <route>` span. Nodejs handlers
489
+ * nest under the request root span; edge handlers become a TOP-LEVEL span on the incoming context
490
+ * exported as runtime 'edge' - Next's sandbox emits it stand-alone, since a production start does not
491
+ * thread the node server's span into the sandbox.
492
+ */
493
+ export function withRouteHandlerSpan<T>(route: string, fn: () => T): T {
494
+ return runHandlerSpan(
495
+ `executing api route (app) ${route}`,
496
+ SPAN_TYPE.runHandler,
497
+ { 'next.route': route },
498
+ fn,
499
+ );
500
+ }
501
+
502
+ function runHandlerSpan<T>(
503
+ name: string,
504
+ spanType: string,
505
+ attributes: Record<string, unknown>,
506
+ fn: () => T,
507
+ ): T {
508
+ const state = stateOf(getWorkUnit());
509
+ if (!state) return fn();
510
+ const { api: a } = state;
511
+ const edge = state.edgeMode === 'handler';
512
+ const parentContext = edge ? state.incomingContext : state.currentContext;
513
+ const tracer = a.trace.getTracer('next.js', '0.0.1');
514
+ const span = tracer.startSpan(
515
+ name,
516
+ {
517
+ kind: a.SpanKind.INTERNAL,
518
+ attributes: {
519
+ ...attributes,
520
+ 'next.span_name': name,
521
+ 'next.span_type': spanType,
522
+ },
523
+ },
524
+ parentContext,
525
+ );
526
+ const spanContext = a.trace.setSpan(parentContext, span);
527
+ const previousContext = state.currentContext;
528
+ state.currentContext = spanContext;
529
+ const settle = (error?: unknown) => {
530
+ state.currentContext = previousContext;
531
+ if (error !== undefined) {
532
+ span.recordException(error);
533
+ span.setStatus({ code: a.SpanStatusCode.ERROR, message: errorMessage(error) });
534
+ } else {
535
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
536
+ }
537
+ endSpan(span, edge ? 'edge' : 'nodejs');
538
+ };
539
+ try {
540
+ const result = a.context.with(spanContext, fn);
541
+ if (isThenable(result)) {
542
+ return (result as PromiseLike<unknown>).then(
543
+ value => {
544
+ settle();
545
+ return value;
546
+ },
547
+ error => {
548
+ settle(error);
549
+ throw error;
550
+ },
551
+ ) as T;
552
+ }
553
+ settle();
554
+ return result;
555
+ } catch (error) {
556
+ settle(error);
557
+ throw error;
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Run a `pages/api` handler invocation inside the `executing api route (pages) <route>` span (no
563
+ * `next.route` attribute - Next omits it here). Nodejs handlers nest under the request root span; edge
564
+ * handlers become a top-level span on the incoming context, the same split as the app handler. Next's
565
+ * pages-api dispatch emits neither `resolve page components` nor `start response`, so the finalizer's
566
+ * start-response leaf is suppressed for the rest of the request.
567
+ */
568
+ export function withPagesApiHandlerSpan<T>(route: string, fn: () => T): T {
569
+ const state = stateOf(getWorkUnit());
570
+ if (state) state.pagesApi = true;
571
+ const name = `executing api route (pages) ${route}`;
572
+ return runHandlerSpan(name, SPAN_TYPE.nodeRunHandler, {}, fn);
573
+ }
574
+
575
+ /**
576
+ * Run a materialized pages-router data fetcher inside its `Render.<kind>` span. Unlike withChildSpan
577
+ * this parents on the request ROOT span - Next runs data fetching beside, not inside, the document
578
+ * render, so the suite asserts it as a SIBLING of the render span.
579
+ *
580
+ * Fallback emulation on the result: a THROW marks this span ERROR and swaps the open render span for
581
+ * Next's `/_error` render tree; `{ notFound: true }` keeps this span clean (Next reports code 0) and
582
+ * swaps the render span for the `/_not-found` resolution. The route's own render span is DISCARDED in
583
+ * both cases - never ended, never exported - matching Next.
584
+ */
585
+ export function withPagesDataSpan<T>(
586
+ kind: 'getServerSideProps' | 'getStaticProps',
587
+ fn: () => T,
588
+ ): T {
589
+ const state = stateOf(getWorkUnit());
590
+ const route = state?.route;
591
+ if (!state || !route) return fn();
592
+ const { api: a } = state;
593
+ const spanType =
594
+ kind === 'getServerSideProps' ? SPAN_TYPE.getServerSideProps : SPAN_TYPE.getStaticProps;
595
+ const name = `${kind} ${route}`;
596
+ const span = a.trace.getTracer('next.js', '0.0.1').startSpan(
597
+ name,
598
+ {
599
+ kind: a.SpanKind.INTERNAL,
600
+ attributes: {
601
+ 'next.route': route,
602
+ 'next.span_name': name,
603
+ 'next.span_type': spanType,
604
+ },
605
+ },
606
+ state.rootContext,
607
+ );
608
+ const settle = (error: unknown, result: unknown) => {
609
+ if (error !== undefined) {
610
+ markSpanError(a, span, error);
611
+ emitPagesErrorFallbackSpans(state, route);
612
+ } else {
613
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
614
+ if ((result as { notFound?: boolean } | null | undefined)?.notFound) {
615
+ emitPagesNotFoundFallbackSpans(state, route);
616
+ }
617
+ }
618
+ endSpan(span, childRuntime(state, spanType));
619
+ };
620
+ try {
621
+ const result = fn();
622
+ if (isThenable(result)) {
623
+ return (result as PromiseLike<unknown>).then(
624
+ value => {
625
+ settle(undefined, value);
626
+ return value;
627
+ },
628
+ (error: unknown) => {
629
+ settle(error ?? new Error('getServerSideProps error'), undefined);
630
+ throw error;
631
+ },
632
+ ) as T;
633
+ }
634
+ settle(undefined, result);
635
+ return result;
636
+ } catch (error) {
637
+ settle(error ?? new Error('getServerSideProps error'), undefined);
638
+ throw error;
639
+ }
640
+ }
641
+
642
+ /** A pre-ended leaf span parented directly on the request root span. */
643
+ function emitRootLeafSpan(
644
+ state: OtelState,
645
+ name: string,
646
+ spanType: string,
647
+ attributes: Record<string, unknown>,
648
+ ): void {
649
+ const { api: a } = state;
650
+ const span = a.trace.getTracer('next.js', '0.0.1').startSpan(
651
+ name,
652
+ {
653
+ kind: a.SpanKind.INTERNAL,
654
+ attributes: {
655
+ ...attributes,
656
+ 'next.span_name': name,
657
+ 'next.span_type': spanType,
658
+ },
659
+ },
660
+ state.rootContext,
661
+ );
662
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
663
+ endSpan(span, 'nodejs');
664
+ }
665
+
666
+ /** Discard the route's open render span (see withPagesDataSpan). */
667
+ function discardRenderSpan(state: OtelState): void {
668
+ state.pagesFallback = true;
669
+ if (state.renderSpan && !state.renderSpan.ended) state.renderSpan.discarded = true;
670
+ }
671
+
672
+ // gSSP threw: Next renders `/_error` (its own renderDocument span), resolves
673
+ // `/_error`, probes `/500` twice (prod), and the route resolve already fired.
674
+ function emitPagesErrorFallbackSpans(state: OtelState, _route: string): void {
675
+ discardRenderSpan(state);
676
+ emitRootLeafSpan(state, 'render route (pages) /_error', SPAN_TYPE.renderDocument, {
677
+ 'next.route': '/_error',
678
+ });
679
+ emitRootLeafSpan(state, 'resolve page components', SPAN_TYPE.findPageComponents, {
680
+ 'next.route': '/_error',
681
+ });
682
+ for (let i = 0; i < 2; i += 1) {
683
+ emitRootLeafSpan(state, 'resolve page components', SPAN_TYPE.findPageComponents, {
684
+ 'next.route': '/500',
685
+ });
686
+ }
687
+ }
688
+
689
+ // gSSP returned { notFound: true }: Next serves the prerendered `/_not-found`
690
+ // (prod: a findPageComponents probe only, no render span).
691
+ function emitPagesNotFoundFallbackSpans(state: OtelState, _route: string): void {
692
+ discardRenderSpan(state);
693
+ emitRootLeafSpan(state, 'resolve page components', SPAN_TYPE.findPageComponents, {
694
+ 'next.route': '/_not-found',
695
+ });
696
+ }
697
+
698
+ /**
699
+ * Leaf `start response` span, which Next emits when the response starts piping. pnext's otel response
700
+ * finalizer calls this right before it closes the root span. Parents under the render span when one is
701
+ * open (page renders), else the root (route handlers). Always exports as runtime 'nodejs', since Next
702
+ * pipes from the node server even for edge routes.
703
+ */
704
+ export function emitStartResponseSpan(): void {
705
+ const state = stateOf(getWorkUnit());
706
+ if (state?.pagesFallback || state?.pagesApi) return;
707
+ emitNodeLeafSpan('start response', SPAN_TYPE.startResponse, {});
708
+ }
709
+
710
+ /**
711
+ * Leaf `clientComponentLoading` span with the number of client component modules loaded during the
712
+ * render. pnext SSRs islands only, so the count reflects pnext's own client-module loads - Next's number
713
+ * additionally counts its internal client components, an internals delta not emulated here. Only
714
+ * emitted for page renders, matching Next.
715
+ */
716
+ export function emitClientComponentLoadingSpan(count: number): void {
717
+ const state = stateOf(getWorkUnit());
718
+ if (!state?.renderSpan || state.renderSpan.ended) return;
719
+ emitNodeLeafSpan(SPAN_TYPE.clientComponentLoading, SPAN_TYPE.clientComponentLoading, {
720
+ 'next.clientComponentLoadCount': count,
721
+ });
722
+ }
723
+
724
+ function emitNodeLeafSpan(
725
+ name: string,
726
+ spanType: string,
727
+ attributes: Record<string, unknown>,
728
+ ): void {
729
+ const state = stateOf(getWorkUnit());
730
+ if (!state) return;
731
+ const { api: a } = state;
732
+ const render = state.renderSpan;
733
+ const parentContext = render && !render.ended ? render.spanContext : state.rootContext;
734
+ const span = a.trace.getTracer('next.js', '0.0.1').startSpan(
735
+ name,
736
+ {
737
+ kind: a.SpanKind.INTERNAL,
738
+ attributes: {
739
+ ...attributes,
740
+ 'next.span_name': name,
741
+ 'next.span_type': spanType,
742
+ },
743
+ },
744
+ parentContext,
745
+ );
746
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
747
+ endSpan(span, 'nodejs');
748
+ }
749
+
750
+ /**
751
+ * Run `fn` inside a `middleware <METHOD>` span (`Middleware.execute`). Unlike
752
+ * withChildSpan this does NOT nest under the request root span: Next's
753
+ * middleware span is a top-level trace parented under the INCOMING traceparent
754
+ * (it runs before BaseServer.handleRequest). We extract the carrier from the
755
+ * request headers so the span inherits the caller's trace/parent, matching
756
+ * `parentId: rootParentId`. Inert (runs `fn` untouched) when otel-api is
757
+ * unavailable. Does not require an open work-unit root span.
758
+ */
759
+ export function withMiddlewareSpan<T>(request: Request, fn: () => T): T {
760
+ const a = api();
761
+ if (!a) return fn();
762
+ const method = request.method.toUpperCase();
763
+ let target = '';
764
+ try {
765
+ const url = new URL(request.url);
766
+ target = url.pathname + url.search;
767
+ } catch {
768
+ target = request.url;
769
+ }
770
+ const carrier: Record<string, string> = {};
771
+ request.headers.forEach((value, key) => {
772
+ carrier[key] = value;
773
+ });
774
+ const parentCtx = a.propagation.extract(a.context.active(), carrier, headerGetter);
775
+ const name = `middleware ${method}`;
776
+ const tracer = a.trace.getTracer('next.js', '0.0.1');
777
+ const span = tracer.startSpan(
778
+ name,
779
+ {
780
+ attributes: {
781
+ 'http.method': method,
782
+ 'http.target': target,
783
+ 'next.span_name': name,
784
+ 'next.span_type': SPAN_TYPE.middleware,
785
+ },
786
+ },
787
+ parentCtx,
788
+ );
789
+ const spanCtx = a.trace.setSpan(parentCtx, span);
790
+ const settle = (error?: unknown) => {
791
+ if (error !== undefined) {
792
+ span.recordException(error);
793
+ span.setStatus({ code: a.SpanStatusCode.ERROR, message: errorMessage(error) });
794
+ } else {
795
+ span.setStatus({ code: a.SpanStatusCode.UNSET });
796
+ }
797
+ // Middleware always runs in the edge runtime in Next; pnext's proxy runner
798
+ // already wraps the handler in withRouteRuntime('edge') — the explicit tag
799
+ // makes the exported runtime deterministic regardless of settle timing.
800
+ endSpan(span, 'edge');
801
+ };
802
+ try {
803
+ const result = a.context.with(spanCtx, fn);
804
+ if (isThenable(result)) {
805
+ return (result as PromiseLike<unknown>).then(
806
+ value => {
807
+ settle();
808
+ return value;
809
+ },
810
+ error => {
811
+ settle(error);
812
+ throw error;
813
+ },
814
+ ) as T;
815
+ }
816
+ settle();
817
+ return result;
818
+ } catch (error) {
819
+ settle(error);
820
+ throw error;
821
+ }
822
+ }
823
+
824
+ // A leaf span with no work inside (e.g. resolve-segment-modules markers).
825
+ export function emitLeafSpan(
826
+ name: string,
827
+ spanType: string,
828
+ attributes: Record<string, unknown>,
829
+ kind?: number,
830
+ ): void {
831
+ if (spanType === SPAN_TYPE.getLayoutOrPageModule) {
832
+ emitSegmentSpan(name, spanType, attributes, kind);
833
+ return;
834
+ }
835
+ withChildSpan(name, spanType, attributes, () => undefined, kind !== undefined ? { kind } : {});
836
+ }
837
+
838
+ function emitSegmentSpan(
839
+ name: string,
840
+ spanType: string,
841
+ attributes: Record<string, unknown>,
842
+ kind?: number,
843
+ ): void {
844
+ const state = stateOf(getWorkUnit());
845
+ if (state?.pagesFallback) return;
846
+ if (!state?.renderSpan || state.renderSpan.ended) {
847
+ withChildSpan(name, spanType, attributes, () => undefined, kind !== undefined ? { kind } : {});
848
+ return;
849
+ }
850
+ const tree = ensureComponentTreeSpan(state);
851
+ const previous = state.currentContext;
852
+ state.currentContext = tree.spanContext;
853
+ try {
854
+ withChildSpan(name, spanType, attributes, () => undefined, kind !== undefined ? { kind } : {});
855
+ } finally {
856
+ state.currentContext = previous;
857
+ }
858
+ }
859
+
860
+ function ensureComponentTreeSpan(state: OtelState) {
861
+ const current = state.componentTreeSpan;
862
+ if (current && !current.ended) return current;
863
+ const { api: a } = state;
864
+ const parentContext = state.renderSpan?.spanContext ?? state.currentContext;
865
+ const name = 'build component tree';
866
+ const span = a.trace.getTracer('next.js', '0.0.1').startSpan(
867
+ name,
868
+ {
869
+ kind: a.SpanKind.INTERNAL,
870
+ attributes: {
871
+ 'next.span_name': name,
872
+ 'next.span_type': SPAN_TYPE.createComponentTree,
873
+ },
874
+ },
875
+ parentContext,
876
+ );
877
+ const spanContext = a.trace.setSpan(parentContext, span);
878
+ const next = { span, parentContext, spanContext, ended: false };
879
+ state.componentTreeSpan = next;
880
+ return next;
881
+ }
882
+
883
+ function endComponentTreeSpan(state: OtelState): void {
884
+ const tree = state.componentTreeSpan;
885
+ if (!tree || tree.ended) return;
886
+ tree.ended = true;
887
+ tree.span.setStatus({ code: state.api.SpanStatusCode.UNSET });
888
+ endSpan(tree.span, childRuntime(state, SPAN_TYPE.createComponentTree));
889
+ if (state.currentContext === tree.spanContext) state.currentContext = tree.parentContext;
890
+ }
891
+
892
+ const headerGetter = {
893
+ keys(carrier: Record<string, string>): string[] {
894
+ return Object.keys(carrier);
895
+ },
896
+ get(carrier: Record<string, string>, key: string): string | undefined {
897
+ return carrier[key.toLowerCase()] ?? carrier[key];
898
+ },
899
+ };
900
+
901
+ /** Setter for propagation.inject into a plain record carrier. */
902
+ export const carrierSetter = {
903
+ set(carrier: Record<string, string>, key: string, value: string): void {
904
+ carrier[key] = value;
905
+ },
906
+ };
907
+
908
+ function errorMessage(error: unknown): string {
909
+ if (error instanceof Error) return error.message;
910
+ return String(error);
911
+ }
912
+
913
+ function markSpanError(api: OtelApi, span: OtelSpan, error: unknown): void {
914
+ span.setAttribute('error.type', error instanceof Error ? error.name : typeof error);
915
+ span.recordException(error);
916
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: errorMessage(error) });
917
+ }
918
+
919
+ export function otelExportInProgress(): boolean {
920
+ return spanEndDepth > 0;
921
+ }
922
+
923
+ function endSpan(span: OtelSpan, runtime?: SpanRuntime): void {
924
+ spanEndDepth++;
925
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
926
+ const previousRuntime = process.env.NEXT_RUNTIME;
927
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
928
+ if (runtime) process.env.NEXT_RUNTIME = runtime;
929
+ try {
930
+ span.end();
931
+ } finally {
932
+ if (runtime) {
933
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
934
+ if (previousRuntime === undefined) delete process.env.NEXT_RUNTIME;
935
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
936
+ else process.env.NEXT_RUNTIME = previousRuntime;
937
+ }
938
+ spanEndDepth--;
939
+ }
940
+ }
941
+
942
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
943
+ return typeof (value as { then?: unknown } | null | undefined)?.then === 'function';
944
+ }