@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,676 @@
1
+ /**
2
+ * The dispatch half of the client action runtime: argument encoding, the POST wire, redirect/refresh/
3
+ * error semantics, action-return revival, and the route error.js overlay body. Nothing here can run
4
+ * before an action actually fires, so action-client.ts reaches it through a dynamic import() and this
5
+ * whole module lands in a deferred chunk.
6
+ *
7
+ * Wire shape (see compat/actions/protocol.ts): POST to the CURRENT page URL with the action id in the
8
+ * `next-action` header. JSON-safe args go as a text/plain JSON array; a single FormData argument posts
9
+ * as multipart; mixed args post as multipart with a JSON args field referencing file slots.
10
+ *
11
+ * Responses: `{ data }` JSON on success; `{ redirect }` JSON for framework redirects (soft-navigated
12
+ * here); text/plain bodies are thrown as Errors (masked in prod, like Next); any other error
13
+ * content-type throws the "unexpected response" error React shows; a 404 with the not-found marker
14
+ * swaps in the server-rendered 404 page.
15
+ */
16
+ import { h, render, type ComponentType } from 'preact';
17
+ import {
18
+ blockJavascriptUrl,
19
+ evictClientRouterCache,
20
+ rearmVisiblePrefetches,
21
+ softNavigate,
22
+ } from '../../api/router';
23
+ import { onNavigationStart } from '../../api/router/events';
24
+ import { UnrecognizedActionError } from './unrecognized-error';
25
+ import {
26
+ ACTION_ID_FIELD,
27
+ ARRAY_BUFFER_MARKER,
28
+ FORM_STATE_FIELD,
29
+ SUBMIT_ACTION_ID_FIELD,
30
+ TYPED_ARRAY_MARKER,
31
+ actionReturnClientReferences,
32
+ reviveBinaryMarker,
33
+ tagActionError,
34
+ type ActionFunction,
35
+ } from './action-shared';
36
+
37
+ const ACTION_ID_HEADER = 'next-action';
38
+ const ACTION_REDIRECT_HEADER = 'x-action-redirect';
39
+ const ACTION_REFRESH_HEADER = 'x-pnext-action-refresh';
40
+ const NEXT_ACTION_REVALIDATED_HEADER = 'x-action-revalidated';
41
+ const ACTION_NOT_FOUND_HEADER = 'x-pnext-action-not-found';
42
+ const NEXT_ACTION_NOT_FOUND_HEADER = 'x-nextjs-action-not-found';
43
+ const ACTION_ERROR_DIGEST_HEADER = 'x-pnext-action-error-digest';
44
+ const ACTION_FLIGHT_CONTENT_TYPE = 'text/x-component';
45
+ const ACTION_ARGS_FIELD = '$pnext_args';
46
+ const ARG_FORMDATA_MARKER = '$$pnext_fd';
47
+ const ARG_FILE_MARKER = '$$pnext_file';
48
+ const ARG_FORMDATA_PREFIX = '$pnext_fd_';
49
+ const ARG_FILE_PREFIX = '$pnext_file_';
50
+ const ARG_ACTION_MARKER = '$$pnext_action_ref';
51
+ const REDIRECT_TYPE_HEADER = 'x-pnext-redirect-type';
52
+ const TEMP_REF_MARKER = '$$pnext_temp_ref';
53
+ const ACTION_STREAM_CONTENT_TYPE = 'x-component/pnext-stream';
54
+ const ACTION_ELEMENT_MARKER = '$$pnext_element';
55
+ const ACTION_ELEMENT_HTML_MARKER = '$$pnext_element_html';
56
+
57
+ const MASKED_ACTION_ERROR =
58
+ '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.';
59
+ const UNEXPECTED_RESPONSE = 'An unexpected response was received from the server.';
60
+
61
+ // Only compared within a single dispatch, so starting the count when this
62
+ // module loads (rather than at page load) changes nothing.
63
+ let clientNavigationGeneration = 0;
64
+ onNavigationStart(() => {
65
+ clientNavigationGeneration += 1;
66
+ });
67
+
68
+ interface ActionFlightPayload {
69
+ a?: unknown;
70
+ f?: { kind?: string; html?: string; url?: string } | '';
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // argument encoding
75
+ // ---------------------------------------------------------------------------
76
+
77
+ function isPlainSerializable(value: unknown, depth = 0): boolean {
78
+ if (value === null || value === undefined) return true;
79
+ const type = typeof value;
80
+ if (type === 'string' || type === 'number' || type === 'boolean') return true;
81
+ if (type !== 'object') return false;
82
+ if (depth > 32) return false;
83
+ if (isBinary(value)) return true; // marker-encoded on the wire
84
+ if (Array.isArray(value)) return value.every(item => isPlainSerializable(item, depth + 1));
85
+ if (value instanceof FormData || isFileLike(value)) return false; // handled by caller
86
+ if (value instanceof Date) return true;
87
+ if (typeof (value as PromiseLike<unknown>).then === 'function') return true;
88
+ if (value instanceof Map) {
89
+ return [...value.entries()].every(
90
+ ([k, v]) => isPlainSerializable(k, depth + 1) && isPlainSerializable(v, depth + 1),
91
+ );
92
+ }
93
+ if (value instanceof Set) return [...value].every(item => isPlainSerializable(item, depth + 1));
94
+ const proto = Object.getPrototypeOf(value) as object | null;
95
+ if (proto !== Object.prototype && proto !== null) return false;
96
+ return Object.values(value as Record<string, unknown>).every(item =>
97
+ isPlainSerializable(item, depth + 1),
98
+ );
99
+ }
100
+
101
+ function isFileLike(value: unknown): value is Blob {
102
+ return typeof Blob !== 'undefined' && value instanceof Blob;
103
+ }
104
+
105
+ function isBinary(value: unknown): value is ArrayBufferView | ArrayBuffer {
106
+ return (
107
+ value instanceof ArrayBuffer || (ArrayBuffer.isView(value) && !(value instanceof DataView))
108
+ );
109
+ }
110
+
111
+ function bytesToBase64(bytes: Uint8Array): string {
112
+ if (typeof btoa === 'function') {
113
+ let binary = '';
114
+ for (const byte of bytes) binary += String.fromCharCode(byte);
115
+ return btoa(binary);
116
+ }
117
+ return Buffer.from(bytes).toString('base64');
118
+ }
119
+
120
+ function encodeBinaryValue(value: ArrayBufferView | ArrayBuffer): unknown {
121
+ if (value instanceof ArrayBuffer) {
122
+ return { [ARRAY_BUFFER_MARKER]: bytesToBase64(new Uint8Array(value)) };
123
+ }
124
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
125
+ return { [TYPED_ARRAY_MARKER]: { type: value.constructor.name, data: bytesToBase64(bytes) } };
126
+ }
127
+
128
+ function containsSpecial(value: unknown): boolean {
129
+ if (value instanceof FormData || isFileLike(value)) return true;
130
+ if (Array.isArray(value)) return value.some(containsSpecial);
131
+ if (value !== null && typeof value === 'object') {
132
+ return Object.values(value as Record<string, unknown>).some(containsSpecial);
133
+ }
134
+ return false;
135
+ }
136
+
137
+ function isActionReference(value: unknown): value is ActionFunction {
138
+ return (
139
+ typeof value === 'function' && typeof (value as ActionFunction).$$pnextActionId === 'string'
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Encode non-JSON argument values React's wire supports: server-action
145
+ * references (fn identity travels as its wire id), Dates, Maps, Sets,
146
+ * undefined, and promises (awaited, like React's encodeReply).
147
+ */
148
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
149
+ return Boolean(value) && typeof (value as PromiseLike<unknown>).then === 'function';
150
+ }
151
+
152
+ async function encodeSpecialValues(value: unknown): Promise<unknown> {
153
+ if (isActionReference(value)) return { [ARG_ACTION_MARKER]: value.$$pnextActionId };
154
+ if (value === undefined) return { $$pnext_undefined: true };
155
+ if (isBinary(value)) return encodeBinaryValue(value);
156
+ if (value instanceof Date) return { $$pnext_date: value.toISOString() };
157
+ if (isThenable(value)) {
158
+ return encodeSpecialValues(await value);
159
+ }
160
+ if (value instanceof Map) {
161
+ return {
162
+ $$pnext_map: await Promise.all(
163
+ [...value.entries()].map(async ([k, v]) => [
164
+ await encodeSpecialValues(k),
165
+ await encodeSpecialValues(v),
166
+ ]),
167
+ ),
168
+ };
169
+ }
170
+ if (value instanceof Set) {
171
+ return { $$pnext_set: await Promise.all([...value].map(encodeSpecialValues)) };
172
+ }
173
+ if (Array.isArray(value)) return Promise.all(value.map(encodeSpecialValues));
174
+ if (
175
+ value !== null &&
176
+ typeof value === 'object' &&
177
+ !(value instanceof FormData) &&
178
+ !isFileLike(value)
179
+ ) {
180
+ const proto = Object.getPrototypeOf(value) as object | null;
181
+ if (proto === Object.prototype || proto === null) {
182
+ const out: Record<string, unknown> = {};
183
+ for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
184
+ out[key] = await encodeSpecialValues(item);
185
+ }
186
+ return out;
187
+ }
188
+ }
189
+ return value;
190
+ }
191
+
192
+ function assertSerializableArg(value: unknown) {
193
+ if (value instanceof FormData || isFileLike(value)) return;
194
+ if (isActionReference(value)) return;
195
+ if (isPlainSerializable(value)) return;
196
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
197
+ const dev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
198
+ throw tagActionError(
199
+ new Error(
200
+ dev
201
+ ? 'Cannot access value on the server. Only plain objects can be passed to Server Actions from Client Components.'
202
+ : MASKED_ACTION_ERROR,
203
+ ),
204
+ );
205
+ }
206
+
207
+ interface EncodedBody {
208
+ body: BodyInit | undefined;
209
+ contentType?: string;
210
+ }
211
+
212
+ /**
213
+ * Temporary references (Next compat): stamp each plain-object node in the args with a unique id and
214
+ * remember id -> original object, so an action returning that same object serializes back to a marker
215
+ * the client swaps for the IDENTICAL reference. Mutates a shallow marker key onto the wire objects
216
+ * only, never the caller's originals, and the returned map resolves markers on the response.
217
+ */
218
+ function tagTempRefs(originals: unknown[], encoded: unknown[]): Map<string, object> {
219
+ const registry = new Map<string, object>();
220
+ let counter = 0;
221
+ const seen = new Set<unknown>();
222
+ const walk = (original: unknown, wire: unknown): void => {
223
+ if (
224
+ original === null ||
225
+ typeof original !== 'object' ||
226
+ wire === null ||
227
+ typeof wire !== 'object'
228
+ ) {
229
+ return;
230
+ }
231
+ if (seen.has(wire)) return;
232
+ seen.add(wire);
233
+ if (Array.isArray(original) && Array.isArray(wire)) {
234
+ for (let i = 0; i < wire.length; i++) walk(original[i], wire[i]);
235
+ return;
236
+ }
237
+ const proto = Object.getPrototypeOf(original) as object | null;
238
+ if (proto !== Object.prototype && proto !== null) return;
239
+ const id = `t${counter++}`;
240
+ registry.set(id, original);
241
+ (wire as Record<string, unknown>)[TEMP_REF_MARKER] = id;
242
+ for (const key of Object.keys(original)) {
243
+ walk((original as Record<string, unknown>)[key], (wire as Record<string, unknown>)[key]);
244
+ }
245
+ };
246
+ for (let i = 0; i < encoded.length; i++) walk(originals[i], encoded[i]);
247
+ return registry;
248
+ }
249
+
250
+ // Temp-ref revival is folded into reviveActionReturn's single walk: a marker
251
+ // hit must return the caller's ORIGINAL object untouched, so no later pass may
252
+ // clone it (a follow-up structural copy would silently break `===` identity —
253
+ // the temporary-references contract).
254
+
255
+ /**
256
+ * Revive an action return in ONE walk: temporary references swap back to the caller's original objects
257
+ * (returned as-is - any further cloning would break the `===` identity), client-component element
258
+ * markers become live vnodes, and binary markers become typed arrays.
259
+ */
260
+ function reviveActionReturn(value: unknown, tempRefs?: Map<string, object>): unknown {
261
+ const walk = (input: unknown): unknown => {
262
+ if (input === null || typeof input !== 'object') return input;
263
+ if (Array.isArray(input)) return input.map(walk);
264
+ const record = input as Record<string, unknown>;
265
+ const tempId = record[TEMP_REF_MARKER];
266
+ if (typeof tempId === 'string' && tempRefs?.has(tempId)) return tempRefs.get(tempId);
267
+ const binary = reviveBinaryMarker(record);
268
+ if (binary !== undefined) return binary;
269
+ // A server-rendered element tree the action returned (host elements / async
270
+ // server components): revive it into a renderable node whose innerHTML is
271
+ // the server markup, so useActionState can drop it straight into the tree.
272
+ const elementHtml = record[ACTION_ELEMENT_HTML_MARKER];
273
+ if (typeof elementHtml === 'string') {
274
+ return h('span', { dangerouslySetInnerHTML: { __html: elementHtml } });
275
+ }
276
+ const encoded = record[ACTION_ELEMENT_MARKER];
277
+ if (encoded && typeof encoded === 'object') {
278
+ const { id, props, key } = encoded as { id?: unknown; props?: unknown; key?: unknown };
279
+ const component = typeof id === 'string' ? actionReturnClientReferences.get(id) : undefined;
280
+ if (!component) {
281
+ throw tagActionError(
282
+ new Error(`Missing client component reference for action return: ${String(id)}`),
283
+ );
284
+ }
285
+ const revivedProps = walk(props);
286
+ const componentProps =
287
+ revivedProps && typeof revivedProps === 'object'
288
+ ? (revivedProps as Record<string, unknown>)
289
+ : {};
290
+ return h(component, key === undefined ? componentProps : { ...componentProps, key });
291
+ }
292
+ const out: Record<string, unknown> = {};
293
+ for (const [key, item] of Object.entries(record)) out[key] = walk(item);
294
+ return out;
295
+ };
296
+ return walk(value);
297
+ }
298
+
299
+ async function encodeArgs(
300
+ rawArgs: unknown[],
301
+ ): Promise<EncodedBody & { tempRefs?: Map<string, object> }> {
302
+ if (rawArgs.length === 0) return { body: '[]', contentType: 'text/plain;charset=UTF-8' };
303
+ for (const arg of rawArgs) assertSerializableArg(arg);
304
+ const args = await Promise.all(rawArgs.map(encodeSpecialValues));
305
+
306
+ // The common form shape: exactly one FormData argument posts as-is.
307
+ if (args.length === 1 && args[0] instanceof FormData) {
308
+ return { body: stripActionFields(args[0]) };
309
+ }
310
+
311
+ if (!args.some(containsSpecial)) {
312
+ const tempRefs = tagTempRefs(rawArgs, args);
313
+ return { body: JSON.stringify(args), contentType: 'text/plain;charset=UTF-8', tempRefs };
314
+ }
315
+
316
+ // Mixed args: multipart with a JSON descriptor referencing part slots.
317
+ const form = new FormData();
318
+ let formDataSlots = 0;
319
+ let fileSlots = 0;
320
+ const encoded = args.map(arg => {
321
+ if (arg instanceof FormData) {
322
+ const slot = formDataSlots++;
323
+ for (const [key, item] of stripActionFields(arg).entries()) {
324
+ form.append(`${ARG_FORMDATA_PREFIX}${slot}:${key}`, item);
325
+ }
326
+ return { [ARG_FORMDATA_MARKER]: slot };
327
+ }
328
+ if (isFileLike(arg)) {
329
+ const slot = fileSlots++;
330
+ form.append(`${ARG_FILE_PREFIX}${slot}`, arg);
331
+ return { [ARG_FILE_MARKER]: slot };
332
+ }
333
+ return arg;
334
+ });
335
+ form.append(ACTION_ARGS_FIELD, JSON.stringify(encoded));
336
+ return { body: form };
337
+ }
338
+
339
+ function stripActionFields(form: FormData): FormData {
340
+ if (
341
+ !form.has(ACTION_ID_FIELD) &&
342
+ !form.has(SUBMIT_ACTION_ID_FIELD) &&
343
+ !form.has(FORM_STATE_FIELD)
344
+ )
345
+ return form;
346
+ const stripped = new FormData();
347
+ for (const [key, item] of form.entries()) {
348
+ if (key !== ACTION_ID_FIELD && key !== SUBMIT_ACTION_ID_FIELD && key !== FORM_STATE_FIELD)
349
+ stripped.append(key, item);
350
+ }
351
+ return stripped;
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // dispatch
356
+ // ---------------------------------------------------------------------------
357
+
358
+ export async function callAction(id: string, args: unknown[]): Promise<unknown> {
359
+ if (typeof Event !== 'undefined' && args.at(-1) instanceof Event) args = args.slice(0, -1);
360
+ const navigationGenerationAtDispatch = clientNavigationGeneration;
361
+ const { body, contentType, tempRefs } = await encodeArgs(args);
362
+ // accept mirrors Next's flight wire; app code observes it via headers().
363
+ const headers: Record<string, string> = {
364
+ [ACTION_ID_HEADER]: id,
365
+ accept: 'text/x-component',
366
+ };
367
+ if (contentType) headers['content-type'] = contentType;
368
+
369
+ let response: Response;
370
+ try {
371
+ response = await fetch(location.href, { method: 'POST', headers, body });
372
+ } catch (error) {
373
+ throw tagActionError(error);
374
+ }
375
+
376
+ // notFound() thrown inside the action: swap in the server-rendered 404 page.
377
+ if (response.status === 404 && response.headers.get(ACTION_NOT_FOUND_HEADER)) {
378
+ await swapDocument(response);
379
+ return undefined;
380
+ }
381
+
382
+ // Version skew: the server did not recognize the action id. React routes this
383
+ // to the nearest error boundary as a typed UnrecognizedActionError (so
384
+ // unstable_isUnrecognizedActionError catches it), NOT a page swap — the 404
385
+ // body here is a plain text/plain message, not renderable HTML.
386
+ if (response.status === 404 && response.headers.get(NEXT_ACTION_NOT_FOUND_HEADER) === '1') {
387
+ const message = await response.text().catch(() => '');
388
+ throw tagActionError(
389
+ new UnrecognizedActionError(
390
+ message ||
391
+ `Server Action "${id}" was not found on the server. \nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action`,
392
+ ),
393
+ );
394
+ }
395
+
396
+ // Single-pass redirect: a 303 with no Location (fetch would have followed
397
+ // one) carrying the destination page HTML. Push history and swap the
398
+ // document — no follow-up request.
399
+ if (response.status === 303) {
400
+ const rearm = hasActionRevalidated(response) ? deferActionPrefetches() : undefined;
401
+ try {
402
+ const target = response.headers.get(ACTION_REDIRECT_HEADER);
403
+ if (target && blockJavascriptUrl(target)) return undefined;
404
+ const contentType = response.headers.get('content-type') ?? '';
405
+ const replace = response.headers.get(REDIRECT_TYPE_HEADER) === 'replace';
406
+ // Single-pass flight redirect: the 303 carries the destination page HTML in
407
+ // the flight envelope. Push history and swap the document in one round trip.
408
+ if (target && contentType.includes(ACTION_FLIGHT_CONTENT_TYPE)) {
409
+ const payload = await readActionFlightPayload(response);
410
+ const flightHtml = payloadHtml(payload);
411
+ if (flightHtml) {
412
+ await replaceDocument(flightHtml, target, replace, hasActionRevalidated(response));
413
+ return undefined;
414
+ }
415
+ await navigate(target, replace);
416
+ return undefined;
417
+ }
418
+ if (target && contentType.includes('text/html')) {
419
+ const html = await response.text();
420
+ await replaceDocument(html, target, replace, hasActionRevalidated(response));
421
+ return undefined;
422
+ }
423
+ if (target) {
424
+ await navigate(target, replace);
425
+ return undefined;
426
+ }
427
+ } finally {
428
+ rearm?.();
429
+ }
430
+ }
431
+
432
+ if (!response.ok) {
433
+ const responseContentType = response.headers.get('content-type') ?? '';
434
+ if (responseContentType.startsWith('text/plain')) {
435
+ const error = new Error(await response.text()) as Error & { digest?: string };
436
+ // The masked prod body hides the message; the digest rides a header so a
437
+ // caught action error still exposes `error.digest` (Next parity).
438
+ const digest = response.headers.get(ACTION_ERROR_DIGEST_HEADER);
439
+ if (digest) error.digest = digest;
440
+ throw tagActionError(error);
441
+ }
442
+ throw tagActionError(new Error(UNEXPECTED_RESPONSE));
443
+ }
444
+
445
+ const responseContentType = response.headers.get('content-type') ?? '';
446
+ // A streamed action return (actions-streaming): the body IS a ReadableStream
447
+ // the caller reads with getReader(). Return it live, unbuffered.
448
+ if (responseContentType.includes(ACTION_STREAM_CONTENT_TYPE)) {
449
+ if (hasActionRevalidated(response)) {
450
+ const rearm = deferActionPrefetches();
451
+ void refresh().finally(rearm);
452
+ }
453
+ return response.body;
454
+ }
455
+ if (responseContentType.includes(ACTION_FLIGHT_CONTENT_TYPE)) {
456
+ const payload = await readActionFlightPayload(response);
457
+ const revalidated = hasActionRevalidated(response);
458
+ const discarded = clientNavigationGeneration !== navigationGenerationAtDispatch;
459
+ const rearm = revalidated ? deferActionPrefetches() : undefined;
460
+ try {
461
+ const target = response.headers.get(ACTION_REDIRECT_HEADER);
462
+ if (target && blockJavascriptUrl(target)) return undefined;
463
+ const replace = response.headers.get(REDIRECT_TYPE_HEADER) === 'replace';
464
+ const flightHtml = payloadHtml(payload);
465
+ if (target && flightHtml) {
466
+ await replaceDocument(flightHtml, target, replace, revalidated);
467
+ return undefined;
468
+ }
469
+ // A completed action's value must reach client state. If navigation
470
+ // superseded it, that value is discarded, so refresh the now-current route.
471
+ if (revalidated && discarded) {
472
+ await refresh();
473
+ } else if (revalidated && flightHtml && payload.a === undefined) {
474
+ await replaceDocument(flightHtml);
475
+ } else if (revalidated) {
476
+ // Revalidation always re-renders the current route (Next applies the
477
+ // revalidated flight even when the action returned a value). No
478
+ // embedded HTML to swap in — refresh through the soft-nav pipeline so
479
+ // every active parallel slot (intercepted modals included) refetches.
480
+ await refresh();
481
+ }
482
+ return reviveActionReturn(payload.a, tempRefs);
483
+ } finally {
484
+ rearm?.();
485
+ }
486
+ }
487
+ if (!responseContentType.includes('application/json')) {
488
+ throw tagActionError(new Error(UNEXPECTED_RESPONSE));
489
+ }
490
+
491
+ const revalidated = hasActionRevalidated(response);
492
+ const rearm = revalidated ? deferActionPrefetches() : undefined;
493
+ try {
494
+ const payload = (await response.json()) as { data?: unknown; redirect?: string };
495
+ if (typeof payload.redirect === 'string') {
496
+ if (blockJavascriptUrl(payload.redirect)) return undefined;
497
+ await navigate(payload.redirect, response.headers.get(REDIRECT_TYPE_HEADER) === 'replace');
498
+ return undefined;
499
+ }
500
+ // Revive binary, temporary-reference, and client-component element markers.
501
+ const data = reviveActionReturn(payload.data, tempRefs);
502
+ // Revalidation always re-renders the current route, even when the action
503
+ // returned a value (Next parity: revalidatePath/Tag in an action refreshes
504
+ // every active parallel slot, intercepted modals included).
505
+ if (revalidated) await refresh();
506
+ return data;
507
+ } finally {
508
+ rearm?.();
509
+ }
510
+ }
511
+
512
+ function deferActionPrefetches(): () => void {
513
+ // The eviction and the later re-arm both queue behind the router runtime's
514
+ // single load promise, so they still land in this order.
515
+ void evictClientRouterCache({ rearmVisiblePrefetches: false });
516
+ return rearmVisiblePrefetches;
517
+ }
518
+
519
+ /**
520
+ * Count of action dispatches that ended in a framework redirect. The
521
+ * useActionState shim uses it to restore initial state in a preserved layout.
522
+ */
523
+ function markActionRedirected(): void {
524
+ const holder = globalThis as { __pnextActionRedirects?: number };
525
+ holder.__pnextActionRedirects = (holder.__pnextActionRedirects ?? 0) + 1;
526
+ }
527
+
528
+ async function navigate(href: string, replace = false) {
529
+ markActionRedirected();
530
+ const url = new URL(href, location.href);
531
+ if (url.origin !== location.origin) {
532
+ location.assign(url.href);
533
+ return;
534
+ }
535
+ try {
536
+ await softNavigate(url.href, replace ? { replace: true } : undefined);
537
+ } catch (error) {
538
+ // A router failure must never strand an action redirect on the old page —
539
+ // fall back to a full browser navigation (Next's MPA fallback shape).
540
+ console.error(error);
541
+ if (replace) location.replace(url.href);
542
+ else location.assign(url.href);
543
+ }
544
+ }
545
+
546
+ async function refresh() {
547
+ try {
548
+ await softNavigate(location.href, { replace: true, scroll: false });
549
+ } catch (error) {
550
+ // A failed refresh must not reject the dispatch: the action's return value
551
+ // still has to reach the caller (the refresh is a best-effort re-render).
552
+ console.error(error);
553
+ }
554
+ }
555
+
556
+ async function swapDocument(response: Response) {
557
+ const html = await response.text();
558
+ await replaceDocument(html);
559
+ }
560
+
561
+ /**
562
+ * `freshSegments`: the action revalidated, so this HTML is the freshly rendered destination - its
563
+ * layouts carry the write and must replace the live ones instead of being grafted over.
564
+ */
565
+ async function replaceDocument(html: string, href?: string, replace = false, freshSegments = false) {
566
+ if (href) {
567
+ markActionRedirected();
568
+ // Route the single-pass redirect's destination HTML through the normal
569
+ // soft-nav pipeline (as a "cached page" so it skips the fetch) instead of
570
+ // `document.write`-ing a whole new document. `document.write` resets the
571
+ // document but NOT the JS module registry/global scope: in production
572
+ // every route shares one router.ts module instance (see the file-top
573
+ // comment), so a raw `history.pushState(null, ...)` here left the
574
+ // in-memory history bookkeeping (entry ids, the entry-document cache used
575
+ // to restore back/forward without a round trip) out of sync with the new
576
+ // entry, breaking back navigation after an action redirect. softNavigate
577
+ // keeps that bookkeeping consistent and still swaps the whole page (no
578
+ // rootLayoutChanged bailout expected here since the target is
579
+ // same-origin/same-app, per the single-pass redirect's own guarantee).
580
+ try {
581
+ await softNavigate(href, {
582
+ replace,
583
+ cachedPage: { html: actionRedirectPage(html), finalUrl: href, ok: true },
584
+ ...(freshSegments ? { freshSegments: true } : {}),
585
+ });
586
+ } catch (error) {
587
+ // Single-pass redirect swap failed in the router: land on the target
588
+ // with a full navigation rather than stranding the user (and the test's
589
+ // waitForElementByCss) on the origin page.
590
+ console.error(error);
591
+ if (replace) location.replace(new URL(href, location.href).href);
592
+ else location.assign(new URL(href, location.href).href);
593
+ }
594
+ return;
595
+ }
596
+ document.open();
597
+ document.write(html);
598
+ document.close();
599
+ }
600
+
601
+ function actionRedirectPage(html: string): string {
602
+ // A server-only destination does not need its route entry. Avoid importing
603
+ // it so the inline action flight remains the redirect's only request.
604
+ if (/<pnext-client(?:\s|>)/.test(html) || html.includes('<!--pnext-client:')) return html;
605
+ const entryScript =
606
+ /\s*<script\b[^>]*\btype=["']module["'][^>]*\bsrc=["'][^"']*-pnext-client\.js["'][^>]*><\/script>/gi;
607
+ return html.replace(entryScript, '');
608
+ }
609
+
610
+ async function readActionFlightPayload(response: Response): Promise<ActionFlightPayload> {
611
+ try {
612
+ const parsed = JSON.parse(await response.text()) as unknown;
613
+ return isActionFlightPayload(parsed) ? parsed : {};
614
+ } catch {
615
+ throw tagActionError(new Error(UNEXPECTED_RESPONSE));
616
+ }
617
+ }
618
+
619
+ function isActionFlightPayload(value: unknown): value is ActionFlightPayload {
620
+ return value !== null && typeof value === 'object';
621
+ }
622
+
623
+ function payloadHtml(payload: ActionFlightPayload): string | undefined {
624
+ const flight = payload.f;
625
+ if (!flight || typeof flight !== 'object') return undefined;
626
+ return flight.kind === 'pnext-html' && typeof flight.html === 'string' ? flight.html : undefined;
627
+ }
628
+
629
+ function hasActionRevalidated(response: Response): boolean {
630
+ return (
631
+ response.headers.has(ACTION_REFRESH_HEADER) ||
632
+ response.headers.has(NEXT_ACTION_REVALIDATED_HEADER)
633
+ );
634
+ }
635
+
636
+ // ---------------------------------------------------------------------------
637
+ // error boundary overlay body (route error.js support for action failures)
638
+ // ---------------------------------------------------------------------------
639
+
640
+ type ErrorComponentType = ComponentType<{ error: Error; reset: () => void }>;
641
+
642
+ interface HiddenElement extends HTMLElement {
643
+ __pnextPrevDisplay?: string;
644
+ }
645
+
646
+ let overlayRoot: HTMLElement | null = null;
647
+
648
+ /**
649
+ * Hide the page and render the route's error component over it. Callers
650
+ * serialize their calls (action-client chains them), so the `overlayRoot` guard
651
+ * still holds across the dynamic import that reaches this module.
652
+ */
653
+ export function showActionErrorOverlay(Component: ErrorComponentType, reason: unknown) {
654
+ if (overlayRoot) return;
655
+ const error = reason instanceof Error ? reason : new Error(String(reason));
656
+ const hidden: HiddenElement[] = [];
657
+ overlayRoot = document.createElement('div');
658
+ for (const child of [...document.body.children]) {
659
+ if (!(child instanceof HTMLElement)) continue;
660
+ if (child.tagName === 'SCRIPT') continue;
661
+ const element = child as HiddenElement;
662
+ element.__pnextPrevDisplay = element.style.display;
663
+ element.style.display = 'none';
664
+ hidden.push(element);
665
+ }
666
+ document.body.appendChild(overlayRoot);
667
+ const reset = () => {
668
+ if (!overlayRoot) return;
669
+ render(null, overlayRoot);
670
+ overlayRoot.remove();
671
+ overlayRoot = null;
672
+ for (const element of hidden) element.style.display = element.__pnextPrevDisplay ?? '';
673
+ void refresh();
674
+ };
675
+ render(h(Component, { error, reset }), overlayRoot);
676
+ }