@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,531 @@
1
+ /**
2
+ * Client-side server-action runtime (Next compat) - the INITIAL half. Installed once per page by the
3
+ * client entry as `window.__PNEXT_ACTIONS__`; the per-module generated stubs and the form-submit
4
+ * interception below both dispatch through it.
5
+ *
6
+ * Only what must exist before the user can interact lives here: the DOM listeners, the
7
+ * `<form action={fn}>` vnode hook, progressive-form enhancement, and synchronous island-prop revival.
8
+ * Everything that can only run once an action has fired - argument encoding, the POST wire,
9
+ * redirect/refresh/error semantics, action-return revival, the error overlay body - lives in
10
+ * action-dispatch.ts and is reached through import(), so it rides a deferred chunk whose fetch
11
+ * overlaps the action's own round trip.
12
+ */
13
+ import { options as preactOptions, type ComponentType, type VNode } from 'preact';
14
+ import { __pnextPushFormStatus } from '../react/dom';
15
+ import {
16
+ ACTION_ID_FIELD,
17
+ FORM_STATE_FIELD,
18
+ SUBMIT_ACTION_ID_FIELD,
19
+ actionReturnClientReferences,
20
+ isActionError,
21
+ reviveBinaryMarker,
22
+ type ActionFunction,
23
+ } from './action-shared';
24
+
25
+ // Out-of-band carrier for a plain server-action form's id once the client
26
+ // runtime is up. React strips the progressive `$ACTION_ID` hidden inputs from a
27
+ // form's DOM on hydration; a SERVER-component form (not preact-managed) keeps
28
+ // its SSR hidden input forever, so after a soft-nav commit the committed
29
+ // `form.innerHTML` leaks `<input name="$pnext_action_id" ...>`. We move the id
30
+ // to this attribute and drop the hidden input (see stripProgressiveActionIds),
31
+ // keeping submission working via the fallbacks that read it.
32
+ const ACTION_ID_ATTR = 'data-pnext-action-id';
33
+ const PROP_ACTION_MARKER = '$$pnextAction';
34
+ const ACTION_CLICK_ID_ATTR = 'data-pnext-action-click';
35
+ const PROP_ERROR_RESET_MARKER = '$$pnextErrorReset';
36
+
37
+ export { isActionError };
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // dispatch entry point (the wire itself is deferred)
41
+ // ---------------------------------------------------------------------------
42
+
43
+ // Deferring the dispatch chunk is the point of this split, but deferring it to the CLICK puts its
44
+ // `import()` between the click and the POST - a task hop, not a microtask, so even a
45
+ // fetched-and-evaluated chunk lands behind the frame and a driver reading the DOM the instant
46
+ // `click()` resolves sees no request. Warm on `pnext:hydrated` and remember the entry point, so the
47
+ // warm call is a microtask that drains in the click's own task.
48
+ const dispatchRuntime = () => import('./action-dispatch');
49
+
50
+ let warmDispatch: ((id: string, args: unknown[]) => Promise<unknown>) | undefined;
51
+
52
+ const loadDispatch = () => dispatchRuntime().then(module => (warmDispatch = module.callAction));
53
+
54
+ function callAction(id: string, args: unknown[]): Promise<unknown> {
55
+ return warmDispatch ? warmDispatch(id, args) : loadDispatch().then(call => call(id, args));
56
+ }
57
+
58
+ function registerClientReference(
59
+ id: string,
60
+ component: ComponentType<Record<string, unknown>>,
61
+ ): void {
62
+ actionReturnClientReferences.set(id, component);
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // props revival (server-action references serialized into island props)
67
+ // ---------------------------------------------------------------------------
68
+
69
+ function reviveProps<T>(value: T): T {
70
+ return reviveValue(value) as T;
71
+ }
72
+
73
+ function reviveValue(value: unknown): unknown {
74
+ if (value === null || typeof value !== 'object') return value;
75
+ if (Array.isArray(value)) return value.map(reviveValue);
76
+ const record = value as Record<string, unknown>;
77
+ const error = reviveErrorMarker(record);
78
+ if (error) return error;
79
+ if (record[PROP_ERROR_RESET_MARKER] === true) return refreshCurrentRoute;
80
+ if (typeof record[PROP_ACTION_MARKER] === 'string') {
81
+ return createAction(record[PROP_ACTION_MARKER]);
82
+ }
83
+ const binary = reviveBinaryMarker(record);
84
+ if (binary !== undefined) return binary;
85
+ const revived: Record<string, unknown> = {};
86
+ for (const [key, item] of Object.entries(record)) revived[key] = reviveValue(item);
87
+ return revived;
88
+ }
89
+
90
+ function reviveErrorMarker(record: Record<string, unknown>): Error | undefined {
91
+ const marker = record.$$pnextError;
92
+ if (marker === null || typeof marker !== 'object') return undefined;
93
+ const data = marker as Record<string, unknown>;
94
+ const error = new Error(typeof data.message === 'string' ? data.message : '');
95
+ if (typeof data.name === 'string') error.name = data.name;
96
+ if (typeof data.digest === 'string') (error as Error & { digest?: string }).digest = data.digest;
97
+ return error;
98
+ }
99
+
100
+ function refreshCurrentRoute(): void {
101
+ location.reload();
102
+ }
103
+
104
+ function createAction(id: string): ActionFunction {
105
+ const action: ActionFunction = async (...args: unknown[]) => callAction(id, args);
106
+ action.$$pnextActionId = id;
107
+ return action;
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // <form action={fn}> interception (preact has no function-action support)
112
+ // ---------------------------------------------------------------------------
113
+
114
+ interface ActionDomNode extends Element {
115
+ __pnextFormAction?: (formData: FormData) => unknown;
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // pre-hydration submit replay
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * A submission the inline early-capture script (compat/actions/early-submit.ts)
124
+ * queued before this runtime loaded: the FormData snapshot taken at submit
125
+ * time (including the submitter's pair) parked on the form node.
126
+ */
127
+ interface QueuedSubmission {
128
+ data: FormData;
129
+ submitter: ActionDomNode | null;
130
+ }
131
+
132
+ interface QueuedForm extends HTMLFormElement {
133
+ __pnextQueuedSubmit?: QueuedSubmission;
134
+ }
135
+
136
+ function takeQueuedSubmission(form: HTMLFormElement): QueuedSubmission | undefined {
137
+ const queued = (form as QueuedForm).__pnextQueuedSubmit;
138
+ if (queued) delete (form as QueuedForm).__pnextQueuedSubmit;
139
+ return queued;
140
+ }
141
+
142
+ /** Dispatch a captured submission with form-status + post-action reset (the
143
+ * same tail the live submit listener runs). */
144
+ function runFormSubmission(
145
+ form: HTMLFormElement,
146
+ formData: FormData,
147
+ dispatch: (formData: FormData) => unknown,
148
+ ): void {
149
+ const pop = pushFormStatus(form, formData);
150
+ void Promise.resolve(dispatch(formData))
151
+ .then(() => {
152
+ // React resets uncontrolled fields after a completed form action.
153
+ form.reset();
154
+ })
155
+ .catch(error => {
156
+ if (!throwActionErrorAt(form, error)) throw error;
157
+ })
158
+ .finally(pop);
159
+ }
160
+
161
+ function throwActionErrorAt(node: Element, error: unknown): boolean {
162
+ const root = node.closest('pnext-client');
163
+ const rootVNode = (root as unknown as { __k?: VNode } | null)?.__k;
164
+ const vnode = rootVNode ? findVNodeForElement(rootVNode, node) : undefined;
165
+ const handler = (preactOptions as typeof preactOptions & {
166
+ __e?: (error: unknown, vnode: VNode, oldVNode: VNode) => void;
167
+ }).__e;
168
+ if (!vnode || !handler) return false;
169
+ handler(error, vnode, vnode);
170
+ return true;
171
+ }
172
+
173
+ function findVNodeForElement(vnode: VNode, element: Element): VNode | undefined {
174
+ if ((vnode as VNode & { __e?: unknown }).__e === element) return vnode;
175
+ const children = (vnode as VNode & { __k?: (VNode | null)[] }).__k;
176
+ if (!children) return undefined;
177
+ for (const child of children) {
178
+ if (!child) continue;
179
+ const found = findVNodeForElement(child, element);
180
+ if (found) return found;
181
+ }
182
+ return undefined;
183
+ }
184
+
185
+ /** Replay a pre-hydration submission the moment the form's function action
186
+ * hydrates (called from the stashed ref below). */
187
+ function replayQueuedSubmission(form: HTMLFormElement, fn: (formData: FormData) => unknown): void {
188
+ const queued = takeQueuedSubmission(form);
189
+ if (queued) runFormSubmission(form, queued.data, fn);
190
+ }
191
+
192
+ /**
193
+ * Take over from the inline early-capture script: stop capturing (the live submit listener is
194
+ * installed now) and, after hydration has had a chance to claim queued forms via their refs, dispatch
195
+ * whatever remains - server-only progressive forms via the id fields, and anything unclaimed falls
196
+ * back to the native submit the capture prevented.
197
+ */
198
+ function adoptEarlySubmissions(): void {
199
+ const holder = window as typeof window & {
200
+ __pnextEarlySubmits?: HTMLFormElement[];
201
+ __pnextRemoveEarlySubmit?: () => void;
202
+ };
203
+ holder.__pnextRemoveEarlySubmit?.();
204
+ const forms = holder.__pnextEarlySubmits;
205
+ if (!forms?.length) return;
206
+ const flush = () => {
207
+ for (const form of forms.splice(0)) {
208
+ const queued = takeQueuedSubmission(form);
209
+ if (!queued) continue; // hydration replayed it already
210
+ const stashed =
211
+ queued.submitter?.__pnextFormAction ??
212
+ (form as unknown as ActionDomNode).__pnextFormAction;
213
+ if (stashed) {
214
+ runFormSubmission(form, queued.data, stashed);
215
+ continue;
216
+ }
217
+ const submitterId =
218
+ queued.submitter?.getAttribute('name') === SUBMIT_ACTION_ID_FIELD
219
+ ? queued.submitter.getAttribute('value')
220
+ : null;
221
+ const id =
222
+ submitterId ??
223
+ form.querySelector<HTMLInputElement>(`input[name="${cssEscape(ACTION_ID_FIELD)}"]`)
224
+ ?.value ??
225
+ form.getAttribute(ACTION_ID_ATTR) ??
226
+ undefined;
227
+ const hasFormState = Boolean(
228
+ form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`),
229
+ );
230
+ if (id && !hasFormState) {
231
+ // Server-only progressive form (no live state to update): dispatch the
232
+ // RPC directly, like the live listener's unhydrated-form branch.
233
+ runFormSubmission(form, queued.data, formData => callAction(id, [formData]));
234
+ } else if (form.isConnected) {
235
+ // A useActionState form whose island never hydrated (or an unknown
236
+ // shape): give it the native progressive POST the capture prevented.
237
+ form.submit();
238
+ }
239
+ }
240
+ };
241
+ // Islands hydrate asynchronously after this runtime installs; give their
242
+ // refs (replayQueuedSubmission) first claim before falling back.
243
+ setTimeout(flush, 300);
244
+ }
245
+
246
+ type FunctionActionProps = Record<string, unknown> & {
247
+ action?: unknown;
248
+ formAction?: unknown;
249
+ ref?: unknown;
250
+ };
251
+
252
+ function installVNodeHook() {
253
+ const previous = preactOptions.vnode?.bind(preactOptions);
254
+ preactOptions.vnode = (vnode: VNode) => {
255
+ previous?.(vnode);
256
+ if (typeof vnode.type !== 'string') return;
257
+ const props = vnode.props as FunctionActionProps;
258
+ if (vnode.type === 'form' && typeof props.action === 'function') {
259
+ stashFunctionAction(vnode, props, 'action');
260
+ }
261
+ if (
262
+ (vnode.type === 'button' || vnode.type === 'input') &&
263
+ typeof props.formAction === 'function'
264
+ ) {
265
+ stashFunctionAction(vnode, props, 'formAction');
266
+ }
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Replace a function action/formAction prop with a ref that stashes the function on the DOM node for
272
+ * the document-level submit listener. The attribute itself is dropped (a function is not a valid URL)
273
+ * - the listener preventDefaults, so the browser never uses the missing attribute.
274
+ *
275
+ * The ref must go on vnode.ref: preact extracts `ref` from props at createElement time, so adding
276
+ * props.ref inside options.vnode is ignored for DOM elements.
277
+ */
278
+ function stashFunctionAction(
279
+ vnode: VNode,
280
+ props: FunctionActionProps,
281
+ key: 'action' | 'formAction',
282
+ ) {
283
+ const fn = props[key] as (formData: FormData) => unknown;
284
+ delete props[key];
285
+ const existingRef = (vnode as { ref?: unknown }).ref;
286
+ (vnode as { ref?: unknown }).ref = (node: ActionDomNode | null) => {
287
+ if (node) {
288
+ node.__pnextFormAction = fn;
289
+ if (key === 'action' && node instanceof HTMLFormElement) {
290
+ enhanceProgressiveForm(node, fn);
291
+ replayQueuedSubmission(node, fn);
292
+ }
293
+ if (key === 'formAction' && node instanceof HTMLElement) {
294
+ const form = (node as { form?: HTMLFormElement | null }).form ?? null;
295
+ const queued = form && (form as QueuedForm).__pnextQueuedSubmit;
296
+ if (form && queued?.submitter === node) {
297
+ delete (form as QueuedForm).__pnextQueuedSubmit;
298
+ runFormSubmission(form, queued.data, fn);
299
+ }
300
+ }
301
+ }
302
+ applyRef(existingRef, node);
303
+ };
304
+ }
305
+
306
+ interface FormStateMeta {
307
+ action?: { $$pnextActionId?: string };
308
+ state?: unknown;
309
+ permalink?: string;
310
+ }
311
+
312
+ /**
313
+ * useActionState forms rendered on the client: mirror the server's progressive enhancement onto the
314
+ * live DOM (method/enctype/hidden id + state fields) so a NATIVE submit - form.submit() fires no
315
+ * submit event our listener could intercept - still posts the multipart action wire. Refreshed on
316
+ * every render, keeping the state field current.
317
+ */
318
+ function enhanceProgressiveForm(form: HTMLFormElement, fn: unknown) {
319
+ const meta = (fn as { $$pnextFormState?: FormStateMeta }).$$pnextFormState;
320
+ if (!meta) return;
321
+ const id = meta.action?.$$pnextActionId;
322
+ if (!id) return;
323
+ form.setAttribute('method', 'post');
324
+ form.setAttribute('enctype', 'multipart/form-data');
325
+ if (meta.permalink) form.setAttribute('action', meta.permalink);
326
+ setHiddenField(form, ACTION_ID_FIELD, id);
327
+ // Non-JSON-serializable state (an action can return React elements, Maps,
328
+ // circular structures, ...) cannot ride the progressive wire; fall back to
329
+ // null rather than throwing inside the render's ref (which would take the
330
+ // whole island down with it).
331
+ let serializedState = 'null';
332
+ try {
333
+ serializedState = JSON.stringify(meta.state ?? null) ?? 'null';
334
+ } catch {
335
+ serializedState = 'null';
336
+ }
337
+ setHiddenField(form, FORM_STATE_FIELD, serializedState);
338
+ }
339
+
340
+ function setHiddenField(form: HTMLFormElement, name: string, value: string) {
341
+ let input = form.querySelector<HTMLInputElement>(
342
+ `input[type="hidden"][name="${cssEscape(name)}"]`,
343
+ );
344
+ if (!input) {
345
+ input = document.createElement('input');
346
+ input.type = 'hidden';
347
+ input.name = name;
348
+ form.appendChild(input);
349
+ }
350
+ input.value = value;
351
+ }
352
+
353
+ function applyRef(ref: unknown, node: Element | null) {
354
+ if (typeof ref === 'function') (ref as (node: Element | null) => void)(node);
355
+ else if (ref && typeof ref === 'object') (ref as { current: Element | null }).current = node;
356
+ }
357
+
358
+ function installSubmitListener() {
359
+ document.addEventListener(
360
+ 'submit',
361
+ event => {
362
+ const form = event.target as HTMLFormElement | null;
363
+ if (!form || !(form instanceof HTMLFormElement)) return;
364
+ const submitter = event.submitter as ActionDomNode | null;
365
+
366
+ const fn =
367
+ submitter?.__pnextFormAction || (form as unknown as ActionDomNode).__pnextFormAction;
368
+
369
+ let dispatch: ((formData: FormData) => unknown) | undefined = fn;
370
+ if (!dispatch) {
371
+ // A useActionState form that never hydrated (rendered by a server
372
+ // component or an unhydrated client layout) has no live state to
373
+ // update — let the native progressive POST run so the server
374
+ // re-renders the page with the new form state.
375
+ if (form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`)) return;
376
+ // Server-rendered progressive form: the id travels in a hidden field
377
+ // (or the submitter button's own name/value pair under its distinct
378
+ // field name, which overrides the form-level default).
379
+ const submitterId =
380
+ submitter?.getAttribute('name') === SUBMIT_ACTION_ID_FIELD
381
+ ? submitter.getAttribute('value')
382
+ : null;
383
+ const field = form.querySelector<HTMLInputElement>(
384
+ `input[name="${cssEscape(ACTION_ID_FIELD)}"]`,
385
+ );
386
+ // The hidden id may have been moved out-of-band (stripProgressiveActionIds).
387
+ const id = submitterId ?? field?.value ?? form.getAttribute(ACTION_ID_ATTR);
388
+ if (!id) return; // not an action form; let the browser submit it
389
+ dispatch = formData => callAction(id, [formData]);
390
+ }
391
+
392
+ event.preventDefault();
393
+ const formData = new FormData(form, (submitter ?? undefined) as HTMLElement | undefined);
394
+ runFormSubmission(form, formData, dispatch);
395
+ },
396
+ // Bubble phase, so app-level onSubmit handlers that preventDefault
397
+ // themselves run first and can opt out of the default action behavior.
398
+ false,
399
+ );
400
+ adoptEarlySubmissions();
401
+ }
402
+
403
+ /**
404
+ * React parity: a hydrated `<form action={serverAction}>` carries no visible
405
+ * `$ACTION_ID` hidden inputs. A preact ISLAND form already loses its SSR hidden
406
+ * input on hydration (the client vnode never renders one), but a SERVER-COMPONENT
407
+ * form is raw HTML preact never manages, so its progressive-enhancement hidden id
408
+ * survives every soft-nav commit and leaks into `form.innerHTML`. Once the client
409
+ * runtime is up (submissions run through it, not the native POST), move each such
410
+ * form's id out-of-band onto `data-pnext-action-id` and drop the hidden input.
411
+ *
412
+ * useActionState forms (`$pnext_form_state` present) are left untouched: a
413
+ * server-rendered one still relies on its hidden id for the NATIVE progressive
414
+ * POST fallback (installSubmitListener bails to native for form-state forms).
415
+ */
416
+ function stripProgressiveActionIds() {
417
+ if (typeof document === 'undefined') return;
418
+ const inputs = document.querySelectorAll<HTMLInputElement>(
419
+ `input[type="hidden"][name="${cssEscape(ACTION_ID_FIELD)}"]`,
420
+ );
421
+ for (const input of inputs) {
422
+ const form = input.form;
423
+ if (!form) continue;
424
+ if (form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`)) continue;
425
+ if (input.value && !form.hasAttribute(ACTION_ID_ATTR)) {
426
+ form.setAttribute(ACTION_ID_ATTR, input.value);
427
+ }
428
+ input.remove();
429
+ }
430
+ }
431
+
432
+ function installClickActionListener() {
433
+ document.addEventListener('click', event => {
434
+ const target =
435
+ event.target instanceof Element
436
+ ? event.target.closest<HTMLElement>(`[${ACTION_CLICK_ID_ATTR}]`)
437
+ : null;
438
+ if (!target || (target instanceof HTMLButtonElement && target.disabled)) return;
439
+ const id = target.getAttribute(ACTION_CLICK_ID_ATTR);
440
+ if (!id) return;
441
+ event.preventDefault();
442
+ void callAction(id, []);
443
+ });
444
+ }
445
+
446
+ function cssEscape(value: string) {
447
+ const escape = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;
448
+ return escape ? escape(value) : value.replace(/[^a-zA-Z0-9_-]/g, match => `\\${match}`);
449
+ }
450
+
451
+ // Form-status feed for useFormStatus (react-dom compat).
452
+ function pushFormStatus(form: HTMLFormElement, data: FormData): () => void {
453
+ try {
454
+ return __pnextPushFormStatus({
455
+ pending: true,
456
+ data,
457
+ method: (form.getAttribute('method') || 'get').toLowerCase(),
458
+ action: form.getAttribute('action') ?? '',
459
+ });
460
+ } catch {
461
+ return () => undefined;
462
+ }
463
+ }
464
+
465
+ // ---------------------------------------------------------------------------
466
+ // error boundary overlay (route error.js support for action failures)
467
+ // ---------------------------------------------------------------------------
468
+
469
+ // React routes action errors (uncatchable transition/dispatch failures) to the nearest error boundary.
470
+ // pnext's server-rendered pages have no client-side boundary components, so the compat entry installs
471
+ // the route's error.js as an overlay. Only the listeners are initial - the overlay body, which needs
472
+ // preact's renderer and the refresh path, rides the deferred dispatch chunk.
473
+ type ErrorComponentType = ComponentType<{ error: Error; reset: () => void }>;
474
+
475
+ let overlayInstalled = false;
476
+
477
+ export function installActionErrorOverlay(Component: ErrorComponentType) {
478
+ if ((!process.browser && typeof window === 'undefined') || overlayInstalled) return;
479
+ overlayInstalled = true;
480
+
481
+ // Chained, not concurrent: two errors arriving before the chunk lands must
482
+ // still see each other's effect on the overlay's "already showing" guard.
483
+ let chain: Promise<unknown> = Promise.resolve();
484
+ const show = (reason: unknown) => {
485
+ chain = chain.then(async () => {
486
+ const { showActionErrorOverlay } = await dispatchRuntime();
487
+ showActionErrorOverlay(Component, reason);
488
+ });
489
+ };
490
+
491
+ window.addEventListener('unhandledrejection', event => {
492
+ if (isActionError(event.reason)) show(event.reason);
493
+ });
494
+ // useActionState rethrows action errors during render; without a class
495
+ // error boundary in the tree the uncaught render error lands here.
496
+ window.addEventListener('error', event => {
497
+ if (isActionError(event.error)) show(event.error);
498
+ });
499
+ window.addEventListener('pnext:action-error', event => {
500
+ show((event as CustomEvent).detail);
501
+ });
502
+ }
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // install
506
+ // ---------------------------------------------------------------------------
507
+
508
+ export function installActionRuntime() {
509
+ if (!process.browser && typeof window === 'undefined') return;
510
+ if (!window.__PNEXT_ACTIONS__) {
511
+ window.__PNEXT_ACTIONS__ = {
512
+ call: callAction,
513
+ createAction,
514
+ registerClientReference,
515
+ reviveProps,
516
+ };
517
+ installSubmitListener();
518
+ installClickActionListener();
519
+ // Every mountRoute (initial hydration + each soft-nav commit) fires
520
+ // `pnext:hydrated`; strip leaked progressive action-id inputs from the
521
+ // freshly committed DOM so a hydrated form matches React (no `$ACTION_ID`).
522
+ // Hydration is also where the dispatch chunk gets warmed (see loadDispatch):
523
+ // past first paint, before any click, and `import()` dedups the repeats.
524
+ window.addEventListener('pnext:hydrated', stripProgressiveActionIds);
525
+ window.addEventListener('pnext:hydrated', loadDispatch);
526
+ stripProgressiveActionIds();
527
+ }
528
+ installVNodeHook();
529
+ }
530
+
531
+ export { createAction, callAction, registerClientReference, reviveProps };