@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,961 @@
1
+ /**
2
+ * Source rewrite for the 'use cache' directive (Next compat). Regex/heuristic based like the
3
+ * 'use server' scanning in compat/actions/detect.ts - not a compiler. Two shapes are handled:
4
+ *
5
+ * 1. Function-level: a function whose body opens with the directive. The directive is stripped and the
6
+ * function's binding is reassigned to `pnextUseCache(id, fn)` at the end of the module (const
7
+ * declarations are rewritten to let).
8
+ * 2. Module-level: a file whose first statement is the directive. Every exported function declaration
9
+ * gets wrapped the same way.
10
+ *
11
+ * Limitations: anonymous default exports, object methods, and function expressions passed inline as
12
+ * arguments are not wrapped.
13
+ */
14
+ import path from 'node:path';
15
+
16
+ const useCacheRuntimePath = path.join(import.meta.dirname, 'use-cache.ts');
17
+
18
+ const quickTest = /(['"])use cache(?:\s*:\s*[\w-]+)?\1/;
19
+ const directiveText = /(['"])use cache(?:\s*:\s*([\w-]+))?\1\s*;?/;
20
+ const rootParamsImportLine =
21
+ /^[ \t]*import\s+\{[\s\S]*?\}\s+from\s+['"]next\/root-params['"]\s*;?[ \t]*(?:\r?\n)?/gm;
22
+
23
+ const boundHelper = '__pnextUseCacheBound';
24
+
25
+ export function rewriteUseCacheSource(source: string, file: string, root?: string): string {
26
+ // `includes` first: over a real module corpus the quoted-directive regex is
27
+ // ~24x the cost of the token scan, and virtually every module fails the token.
28
+ if (!source.includes('use cache') || !quickTest.test(source)) return source;
29
+
30
+ const idBase = root ? path.relative(root, file) : file;
31
+ const wrapped = new Set<string>();
32
+ const privateWrapped = new Set<string>();
33
+ let out = source;
34
+ let usedBoundHelper = false;
35
+
36
+ out = rewriteModuleLevelDirective(out, wrapped);
37
+ // Nested cached closures capturing enclosing bindings (bound args) must be wrapped in place - a
38
+ // module-level reassignment cannot reach a function-scoped binding, and the captured values
39
+ // participate in the cache key. Runs before the top-level pass so it only claims genuinely-nested
40
+ // directives. Returned arrow closures capture their enclosing scope's free variables as bound args,
41
+ // including any non-serializable function, which is a build error - wrap them first so the directive
42
+ // is claimed here before the nested-named-closure pass, whose brace-balance heuristic would otherwise
43
+ // mis-attribute the arrow's directive to a sibling function declaration.
44
+ const arrows = rewriteReturnedArrowClosures(out, idBase);
45
+ out = arrows.source;
46
+ usedBoundHelper = arrows.wrapped;
47
+ const nested = rewriteNestedBoundClosures(out, idBase);
48
+ out = nested.source;
49
+ usedBoundHelper = usedBoundHelper || nested.wrapped;
50
+ // Function expressions in value position (JSX attributes / call arguments):
51
+ // `<Form foo={async function () { 'use cache'; … }} />`.
52
+ const expressions = rewriteExpressionClosures(out, idBase);
53
+ out = expressions.source;
54
+ usedBoundHelper = usedBoundHelper || expressions.wrapped;
55
+ const objectMethods = rewriteObjectMethods(out, idBase);
56
+ out = objectMethods.source;
57
+ usedBoundHelper = usedBoundHelper || objectMethods.wrapped;
58
+ const staticMethods = rewriteStaticClassMethods(out, idBase);
59
+ out = staticMethods.source;
60
+ const usedDirectHelper = staticMethods.wrapped;
61
+ out = rewriteFunctionLevelDirectives(out, wrapped, privateWrapped);
62
+
63
+ if (wrapped.size === 0 && !usedBoundHelper && !usedDirectHelper) return out;
64
+
65
+ const imports: string[] = [];
66
+ if (wrapped.size > 0 || usedDirectHelper) {
67
+ imports.push(`import { pnextUseCache as __pnextUseCache } from ${JSON.stringify(useCacheRuntimePath)};`);
68
+ }
69
+ if (privateWrapped.size > 0) {
70
+ imports.push(
71
+ `import { pnextUseCachePrivate as __pnextUseCachePrivate } from ${JSON.stringify(useCacheRuntimePath)};`,
72
+ );
73
+ }
74
+ if (usedBoundHelper) {
75
+ imports.push(
76
+ `import { pnextUseCacheBound as ${boundHelper} } from ${JSON.stringify(useCacheRuntimePath)};`,
77
+ );
78
+ }
79
+ // `export default NAME;` snapshots the binding's value when the statement
80
+ // evaluates (a default-exported identifier is not a live binding, unlike
81
+ // `export default function NAME`), so the module-end reassignment would be
82
+ // invisible to importers. Move the statement after the reassignments.
83
+ let defaultExport = '';
84
+ for (const name of wrapped) {
85
+ const statement = new RegExp(
86
+ `(^|[\\n;])[ \\t]*export\\s+default\\s+${escapeIdent(name)}(?![\\w$])\\s*;?`,
87
+ ).exec(out);
88
+ if (!statement) continue;
89
+ out =
90
+ out.slice(0, statement.index) +
91
+ (statement[1] ?? '') +
92
+ out.slice(statement.index + statement[0].length);
93
+ defaultExport = `\nexport default ${name};`;
94
+ break;
95
+ }
96
+
97
+ const hoisted = hoistRootParamsImports(out);
98
+ const assignments = [...wrapped]
99
+ .map(name =>
100
+ privateWrapped.has(name)
101
+ ? `${name} = __pnextUseCachePrivate(${JSON.stringify(`${idBase}#${name}`)}, ${name});`
102
+ : `${name} = __pnextUseCache(${JSON.stringify(`${idBase}#${name}`)}, ${name});`,
103
+ )
104
+ .join('\n');
105
+ const suffix = assignments ? `\n;${assignments}${defaultExport}\n` : '\n';
106
+ return `${hoisted.imports}${imports.join('\n')}\n${hoisted.source}${suffix}`;
107
+ }
108
+
109
+ function hoistRootParamsImports(source: string): { imports: string; source: string } {
110
+ if (!source.includes('next/root-params')) return { imports: '', source };
111
+ const imports: string[] = [];
112
+ const body = source.replace(rootParamsImportLine, statement => {
113
+ imports.push(statement.trimEnd());
114
+ return '';
115
+ });
116
+ if (imports.length === 0) return { imports: '', source };
117
+ return {
118
+ imports: `${imports.join('\n')}\n`,
119
+ source: body.replace(/^\s*\n/, ''),
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Wrap nested cached closures (a `'use cache'` function declared inside another function) in place with
125
+ * `pnextUseCacheBound`, threading the enclosing function's parameters that the closure references as
126
+ * bound-arg key inputs. Handles the load-bearing shape:
127
+ *
128
+ * async function outer(arg) {
129
+ * async function inner() { 'use cache'; return { arg } }
130
+ * return inner()
131
+ * }
132
+ *
133
+ * Captures are limited to the nearest enclosing function's parameter names that appear as whole-word
134
+ * identifiers in the closure body - enough for the fixtures without a full scope analysis, and
135
+ * conservative (never captures globals).
136
+ */
137
+ function rewriteNestedBoundClosures(
138
+ source: string,
139
+ idBase: string,
140
+ ): { source: string; wrapped: boolean } {
141
+ let out = source;
142
+ let wrapped = false;
143
+ let searchFrom = 0;
144
+ for (;;) {
145
+ const tail = out.slice(searchFrom);
146
+ const directive = directiveText.exec(tail);
147
+ if (!directive) break;
148
+ const index = searchFrom + directive.index;
149
+ const prefix = out.slice(0, index);
150
+ if (!/\{\s*$/.test(prefix)) {
151
+ searchFrom = index + directive[0].length;
152
+ continue;
153
+ }
154
+ const closure = findNestedClosure(out, index);
155
+ if (!closure) {
156
+ searchFrom = index + directive[0].length;
157
+ continue;
158
+ }
159
+ const id = `${idBase}#${closure.enclosingName}.${closure.name}`;
160
+ const captureList = closure.captures.length ? closure.captures.join(', ') : '';
161
+ // Replace `[async ]function NAME(params) {` with a wrapped const binding, and
162
+ // strip the directive from the body.
163
+ const wrappedHead =
164
+ `const ${closure.name} = ${boundHelper}(${JSON.stringify(id)}, ${closure.asyncKeyword}function ${closure.name}(${closure.params}) {`;
165
+ const bodyAfterDirective = out.slice(index + directive[0].length, closure.bodyEnd);
166
+ const rest = out.slice(closure.bodyEnd);
167
+ out =
168
+ out.slice(0, closure.declStart) +
169
+ wrappedHead +
170
+ bodyAfterDirective +
171
+ `}, () => [${captureList}])` +
172
+ rest.replace(/^\}/, '');
173
+ wrapped = true;
174
+ searchFrom = closure.declStart + wrappedHead.length;
175
+ }
176
+ return { source: out, wrapped };
177
+ }
178
+
179
+ /**
180
+ * Wrap `use cache` closures returned as arrow functions from an enclosing function - the shape the
181
+ * close-over-function fixture uses:
182
+ *
183
+ * function createCachedFn(start) {
184
+ * function fn() { return start }
185
+ * return async () => { 'use cache'; return Math.random() + fn() }
186
+ * }
187
+ *
188
+ * The capture list is the enclosing function's params and local declarations that the closure
189
+ * references, so a captured function reaches the runtime's close-over-function guard. Scoped to
190
+ * return-position arrows to keep the regex heuristic narrow; arrows elsewhere stay inert.
191
+ */
192
+ function rewriteReturnedArrowClosures(
193
+ source: string,
194
+ idBase: string,
195
+ ): { source: string; wrapped: boolean } {
196
+ let out = source;
197
+ let wrapped = false;
198
+ let searchFrom = 0;
199
+ let counter = 0;
200
+ for (;;) {
201
+ const tail = out.slice(searchFrom);
202
+ const directive = directiveText.exec(tail);
203
+ if (!directive) break;
204
+ const index = searchFrom + directive.index;
205
+ const prefix = out.slice(0, index);
206
+ if (!/\{\s*$/.test(prefix)) {
207
+ searchFrom = index + directive[0].length;
208
+ continue;
209
+ }
210
+ const closure = findReturnedArrowClosure(out, index);
211
+ if (!closure) {
212
+ searchFrom = index + directive[0].length;
213
+ continue;
214
+ }
215
+ const id = `${idBase}#${closure.enclosingName}.arrow${counter === 0 ? '' : counter}`;
216
+ counter += 1;
217
+ const captureList = closure.captures.join(', ');
218
+ const wrappedHead = `${boundHelper}(${JSON.stringify(id)}, ${closure.head}`;
219
+ const bodyAfterDirective = out.slice(index + directive[0].length, closure.bodyEnd);
220
+ const rest = out.slice(closure.bodyEnd);
221
+ out =
222
+ out.slice(0, closure.declStart) +
223
+ wrappedHead +
224
+ bodyAfterDirective +
225
+ `}, () => [${captureList}])` +
226
+ rest.replace(/^\}/, '');
227
+ wrapped = true;
228
+ searchFrom = closure.declStart + wrappedHead.length;
229
+ }
230
+ return { source: out, wrapped };
231
+ }
232
+
233
+ /**
234
+ * Wrap `use cache` function expressions in VALUE position - JSX attributes, call arguments and
235
+ * object-property values. Captures are the nearest enclosing named function's params/locals referenced
236
+ * in the body (same policy as the returned-arrow pass). Statement declarations and top-level bindings
237
+ * are NOT claimed here - the value-position context requires a preceding `={`, `(`, `,` or `:` - so the
238
+ * established passes keep their shapes and id scheme. Named-handler variants are left inert.
239
+ */
240
+ function rewriteExpressionClosures(
241
+ source: string,
242
+ idBase: string,
243
+ ): { source: string; wrapped: boolean } {
244
+ let out = source;
245
+ let wrapped = false;
246
+ let searchFrom = 0;
247
+ let counter = 0;
248
+ for (;;) {
249
+ const tail = out.slice(searchFrom);
250
+ const directive = directiveText.exec(tail);
251
+ if (!directive) break;
252
+ const index = searchFrom + directive.index;
253
+ if (directive[2] !== undefined || !/\{\s*$/.test(out.slice(0, index))) {
254
+ searchFrom = index + directive[0].length;
255
+ continue;
256
+ }
257
+ const closure = findExpressionClosure(out, index);
258
+ if (!closure) {
259
+ searchFrom = index + directive[0].length;
260
+ continue;
261
+ }
262
+ const id = `${idBase}#${closure.enclosingName}.expr${counter === 0 ? '' : counter}`;
263
+ counter += 1;
264
+ const wrappedHead = `${boundHelper}(${JSON.stringify(id)}, ${closure.head}`;
265
+ const bodyAfterDirective = out.slice(index + directive[0].length, closure.bodyEnd);
266
+ const rest = out.slice(closure.bodyEnd);
267
+ out =
268
+ out.slice(0, closure.declStart) +
269
+ wrappedHead +
270
+ bodyAfterDirective +
271
+ `}, () => [${closure.captures.join(', ')}])` +
272
+ rest.replace(/^\}/, '');
273
+ wrapped = true;
274
+ searchFrom = closure.declStart + wrappedHead.length;
275
+ }
276
+ return { source: out, wrapped };
277
+ }
278
+
279
+ function findExpressionClosure(
280
+ source: string,
281
+ directiveIndex: number,
282
+ ): ReturnedArrowClosure | null {
283
+ const prefix = source.slice(0, directiveIndex);
284
+ // Value-position contexts: a JSX attribute (`={`), a call argument (`(` or
285
+ // `,`), or an object-property value (`:`), followed by a function expression
286
+ // or an arrow whose body opens with the directive.
287
+ const exprRe =
288
+ /(=\s*\{|[(,:])\s*((?:async\s+)?(?:function(?:\s+[A-Za-z_$][\w$]*)?\s*\(([^)]*)\)|(?:\(([^)]*)\)|[A-Za-z_$][\w$]*)\s*=>)\s*\{)/g;
289
+ let match: RegExpExecArray | null;
290
+ let best: { headStart: number; head: string; params: string; bodyOpen: number } | null = null;
291
+ while ((match = exprRe.exec(prefix)) !== null) {
292
+ const head = match[2]!;
293
+ const headStart = match.index + match[0].length - head.length;
294
+ const bodyOpen = match.index + match[0].length;
295
+ const between = source.slice(bodyOpen, directiveIndex);
296
+ // The directive must be the first statement of THIS body: zero net braces
297
+ // between, and the body must never close in between (a dip below zero means
298
+ // this candidate's body ended before the directive).
299
+ let balance = 0;
300
+ let closed = false;
301
+ for (const ch of between) {
302
+ if (ch === '{') balance += 1;
303
+ else if (ch === '}') {
304
+ balance -= 1;
305
+ if (balance < 0) {
306
+ closed = true;
307
+ break;
308
+ }
309
+ }
310
+ }
311
+ if (!closed && balance === 0) {
312
+ best = { headStart, head, params: match[3] ?? match[4] ?? '', bodyOpen };
313
+ }
314
+ }
315
+ if (!best) return null;
316
+
317
+ const bodyEnd = matchClosingBrace(source, best.bodyOpen);
318
+ if (bodyEnd < directiveIndex) return null;
319
+ const body = source.slice(best.bodyOpen, bodyEnd);
320
+
321
+ const enclosing = findEnclosingFunctionFull(source, best.headStart);
322
+ const ownParams = new Set(paramNames(best.params));
323
+ const captures = enclosing
324
+ ? enclosingCaptureCandidates(source, enclosing).filter(
325
+ name => !ownParams.has(name) && new RegExp(`\\b${escapeIdent(name)}\\b`).test(body),
326
+ )
327
+ : [];
328
+
329
+ return {
330
+ head: best.head,
331
+ declStart: best.headStart,
332
+ bodyEnd,
333
+ enclosingName: enclosing?.name ?? 'module',
334
+ captures,
335
+ };
336
+ }
337
+
338
+ interface ReturnedArrowClosure {
339
+ head: string;
340
+ declStart: number;
341
+ bodyEnd: number;
342
+ enclosingName: string;
343
+ captures: string[];
344
+ }
345
+
346
+ function findReturnedArrowClosure(
347
+ source: string,
348
+ directiveIndex: number,
349
+ ): ReturnedArrowClosure | null {
350
+ const prefix = source.slice(0, directiveIndex);
351
+ // The parameter list must stay on one line and carry no `<`: otherwise
352
+ // `return (\n <Form getDate={async () => {` reads as one giant "arrow" whose
353
+ // params run from the returned parenthesis to the first `)` inside the JSX,
354
+ // and the wrap lands on the return expression instead of the attribute's
355
+ // closure (use-cache-with-server-function-props). Value-position closures like
356
+ // that are claimed by rewriteExpressionClosures instead.
357
+ const arrowRe = /\breturn\s+(async\s+)?(\([^)<\n]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{/g;
358
+ let match: RegExpExecArray | null;
359
+ let best: { headStart: number; head: string; params: string; bodyOpen: number } | null = null;
360
+ while ((match = arrowRe.exec(prefix)) !== null) {
361
+ const retPrefix = /^return\s+/.exec(match[0])?.[0] ?? 'return ';
362
+ const headStart = match.index + retPrefix.length;
363
+ const bodyOpen = match.index + match[0].length;
364
+ const between = source.slice(bodyOpen, directiveIndex);
365
+ let balance = 0;
366
+ for (const ch of between) {
367
+ if (ch === '{') balance += 1;
368
+ else if (ch === '}') balance -= 1;
369
+ }
370
+ if (balance === 0) {
371
+ const paramSpec = match[2]!;
372
+ const params = paramSpec.startsWith('(') ? paramSpec.slice(1, -1) : paramSpec;
373
+ best = { headStart, head: match[0].slice(retPrefix.length), params, bodyOpen };
374
+ }
375
+ }
376
+ if (!best) return null;
377
+
378
+ const enclosing = findEnclosingFunctionFull(source, best.headStart);
379
+ if (!enclosing) return null;
380
+
381
+ const bodyEnd = matchClosingBrace(source, best.bodyOpen);
382
+ if (bodyEnd < 0) return null;
383
+ const body = source.slice(best.bodyOpen, bodyEnd);
384
+
385
+ const arrowParams = new Set(paramNames(best.params));
386
+ const candidates = enclosingCaptureCandidates(source, enclosing);
387
+ const captures = candidates.filter(
388
+ name => !arrowParams.has(name) && new RegExp(`\\b${escapeIdent(name)}\\b`).test(body),
389
+ );
390
+
391
+ return {
392
+ head: best.head,
393
+ declStart: best.headStart,
394
+ bodyEnd,
395
+ enclosingName: enclosing.name,
396
+ captures,
397
+ };
398
+ }
399
+
400
+ /** Nearest enclosing named function wrapping `pos`, with its body span. */
401
+ function findEnclosingFunctionFull(
402
+ source: string,
403
+ pos: number,
404
+ ): { name: string; params: string[]; bodyStart: number; bodyEnd: number } | null {
405
+ const before = source.slice(0, pos);
406
+ const re = /(?:^|[\s;{}])(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
407
+ let match: RegExpExecArray | null;
408
+ let candidate: { name: string; params: string[]; bodyStart: number; bodyEnd: number } | null =
409
+ null;
410
+ while ((match = re.exec(before)) !== null) {
411
+ const kwOffset = match[0].search(/async|function/);
412
+ if (!isStatementFunctionHead(source, match.index + kwOffset)) continue;
413
+ const bodyOpen = match.index + match[0].length;
414
+ const close = matchClosingBrace(source, bodyOpen);
415
+ if (close > pos) {
416
+ candidate = {
417
+ name: match[1]!,
418
+ params: paramNames(match[2]!),
419
+ bodyStart: bodyOpen,
420
+ bodyEnd: close,
421
+ };
422
+ }
423
+ }
424
+ return candidate;
425
+ }
426
+
427
+ /**
428
+ * Identifiers declared in the enclosing function that a captured arrow may close over: the function's
429
+ * params plus its local function/const/let/var declarations. Over-collection is harmless - only names
430
+ * actually referenced in the closure body become captures.
431
+ */
432
+ function enclosingCaptureCandidates(
433
+ source: string,
434
+ enclosing: { params: string[]; bodyStart: number; bodyEnd: number },
435
+ ): string[] {
436
+ const names = new Set(enclosing.params);
437
+ const body = source.slice(enclosing.bodyStart, enclosing.bodyEnd);
438
+ for (const match of body.matchAll(/\bfunction\s*\*?\s*([A-Za-z_$][\w$]*)/g)) {
439
+ if (match[1]) names.add(match[1]);
440
+ }
441
+ for (const match of body.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g)) {
442
+ if (match[1]) names.add(match[1]);
443
+ }
444
+ return [...names];
445
+ }
446
+
447
+ interface NestedClosure {
448
+ name: string;
449
+ params: string;
450
+ asyncKeyword: string;
451
+ declStart: number;
452
+ bodyEnd: number;
453
+ enclosingName: string;
454
+ captures: string[];
455
+ }
456
+
457
+ /**
458
+ * Given the index of a `'use cache'` directive that opens a function body, find the immediately
459
+ * enclosing named function declaration nested inside another named function and compute its captured
460
+ * enclosing-parameter identifiers. Returns null when the directive does not open one - object methods,
461
+ * statics and arrows are left inert, which is safe: they run uncached rather than breaking the build.
462
+ */
463
+ function findNestedClosure(source: string, directiveIndex: number): NestedClosure | null {
464
+ const prefix = source.slice(0, directiveIndex);
465
+ // Match the nearest `[async ]function NAME(params) {` whose brace opens the
466
+ // directive body (exactly one unclosed brace between it and the directive).
467
+ const declRe = /(?:^|[\s;{}])(async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
468
+ let match: RegExpExecArray | null;
469
+ let best: { asyncKeyword: string; name: string; params: string; headStart: number; bodyOpen: number } | null =
470
+ null;
471
+ while ((match = declRe.exec(prefix)) !== null) {
472
+ // Clean head start: the position of the `async`/`function` keyword (the
473
+ // match may include a leading whitespace/`{`/`;` separator char).
474
+ const kwOffset = match[0].search(/async|function/);
475
+ const cleanStart = match.index + kwOffset;
476
+ if (!isStatementFunctionHead(source, cleanStart)) continue;
477
+ const bodyOpen = match.index + match[0].length; // just after `{`
478
+ // Balance braces from bodyOpen to directiveIndex — must be exactly 0 open
479
+ // *within* this function head region, i.e. the directive is the first stmt.
480
+ const between = source.slice(bodyOpen, directiveIndex);
481
+ let balance = 0;
482
+ for (const ch of between) {
483
+ if (ch === '{') balance += 1;
484
+ else if (ch === '}') balance -= 1;
485
+ }
486
+ if (balance === 0) {
487
+ if (match[2] === 'function') continue;
488
+ best = {
489
+ asyncKeyword: match[1] ? 'async ' : '',
490
+ name: match[2]!,
491
+ params: match[3]!.trim(),
492
+ headStart: cleanStart,
493
+ bodyOpen,
494
+ };
495
+ }
496
+ }
497
+ if (!best) return null;
498
+
499
+ // The closure must be genuinely nested: there is an enclosing named function
500
+ // whose body contains `best`. Find the nearest such enclosing declaration.
501
+ const enclosing = findEnclosingNamedFunction(source, best.headStart);
502
+ if (!enclosing) return null;
503
+
504
+ // Find the matching close brace of the closure body.
505
+ const bodyEnd = matchClosingBrace(source, best.bodyOpen);
506
+ if (bodyEnd < 0) return null;
507
+ const body = source.slice(best.bodyOpen, bodyEnd);
508
+
509
+ // Captures: enclosing params referenced (whole-word) in the closure body and
510
+ // not shadowed by the closure's own params.
511
+ const closureParams = new Set(paramNames(best.params));
512
+ const captures = enclosing.params.filter(
513
+ param => !closureParams.has(param) && new RegExp(`\\b${escapeIdent(param)}\\b`).test(body),
514
+ );
515
+
516
+ return {
517
+ name: best.name,
518
+ params: best.params,
519
+ asyncKeyword: best.asyncKeyword,
520
+ declStart: best.headStart,
521
+ bodyEnd,
522
+ enclosingName: enclosing.name,
523
+ captures,
524
+ };
525
+ }
526
+
527
+ /** Nearest enclosing `[async ]function NAME(params)` whose body wraps `pos`. */
528
+ function findEnclosingNamedFunction(
529
+ source: string,
530
+ pos: number,
531
+ ): { name: string; params: string[] } | null {
532
+ const before = source.slice(0, pos);
533
+ const re = /(?:^|[\s;{}])(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
534
+ let match: RegExpExecArray | null;
535
+ let candidate: { name: string; params: string[] } | null = null;
536
+ while ((match = re.exec(before)) !== null) {
537
+ const kwOffset = match[0].search(/async|function/);
538
+ if (!isStatementFunctionHead(source, match.index + kwOffset)) continue;
539
+ const bodyOpen = match.index + match[0].length;
540
+ const close = matchClosingBrace(source, bodyOpen);
541
+ if (close > pos) {
542
+ candidate = { name: match[1]!, params: paramNames(match[2]!) };
543
+ }
544
+ }
545
+ return candidate;
546
+ }
547
+
548
+ function isStatementFunctionHead(source: string, index: number): boolean {
549
+ if (/(^|[\s;{}])export\s+(?:default\s+)?$/.test(source.slice(0, index))) return true;
550
+ let previous = index - 1;
551
+ while (previous >= 0 && /\s/.test(source[previous]!)) previous -= 1;
552
+ if (previous < 0) return true;
553
+ if (source[previous] === '=') return false;
554
+ if (source[previous] === '{') {
555
+ let beforeBrace = previous - 1;
556
+ while (beforeBrace >= 0 && /\s/.test(source[beforeBrace]!)) beforeBrace -= 1;
557
+ return source[beforeBrace] !== '=';
558
+ }
559
+ return source[previous] === ';' || source[previous] === '}';
560
+ }
561
+
562
+ /** Index just past the `}` matching the `{` at `openIndex`, or -1. */
563
+ function matchClosingBrace(source: string, openIndex: number): number {
564
+ let balance = 1;
565
+ for (let i = openIndex; i < source.length; i += 1) {
566
+ const ch = source[i];
567
+ if (ch === '{') balance += 1;
568
+ else if (ch === '}') {
569
+ balance -= 1;
570
+ if (balance === 0) return i;
571
+ }
572
+ }
573
+ return -1;
574
+ }
575
+
576
+ function escapeIdent(name: string): string {
577
+ return name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
578
+ }
579
+
580
+ /** Extract simple parameter identifier names from a `(...)` param string. */
581
+ function paramNames(params: string): string[] {
582
+ const names: string[] = [];
583
+ for (const part of params.split(',')) {
584
+ const name = part.trim().split(/[=:\s]/)[0];
585
+ if (name) names.push(name);
586
+ }
587
+ return names;
588
+ }
589
+
590
+ /**
591
+ * Module-level directive: strip it and wrap every exported function-ish
592
+ * declaration. `export const` becomes `export let` so the binding is
593
+ * reassignable.
594
+ */
595
+ function rewriteModuleLevelDirective(source: string, wrapped: Set<string>): string {
596
+ const prologue = /^(?:#![^\n]*\n)?(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*/.exec(source);
597
+ const offset = prologue?.[0].length ?? 0;
598
+ const head = source.slice(offset);
599
+ const directive = /^(['"])use cache(?:\s*:\s*[\w-]+)?\1\s*;?/.exec(head);
600
+ if (!directive) return source;
601
+
602
+ let out = source.slice(0, offset) + head.slice(directive[0].length);
603
+
604
+ // export [async] function NAME / export [default] [async] function NAME
605
+ for (const match of out.matchAll(
606
+ /\bexport\s+(?:default\s+)?(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)/g,
607
+ )) {
608
+ if (match[1]) wrapped.add(match[1]);
609
+ }
610
+ // export const|let|var NAME = ... (assume function values; const -> let)
611
+ out = out.replace(
612
+ /\bexport\s+(const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g,
613
+ (full, keyword: string, name: string) => {
614
+ wrapped.add(name);
615
+ return full.replace(keyword, keyword === 'const' ? 'let' : keyword);
616
+ },
617
+ );
618
+
619
+ // `const NAME = ...; export { NAME }` — a re-exported binding under a
620
+ // module-level directive. Wrap each re-exported name declared as a top-level
621
+ // const/let/var function value (flip its const to let so it's reassignable).
622
+ const reExported = new Set<string>();
623
+ for (const clause of out.matchAll(/\bexport\s*\{([^}]*)\}/g)) {
624
+ for (const spec of clause[1]!.split(',')) {
625
+ const local = spec.trim().split(/\s+as\s+/)[0]?.trim();
626
+ if (local && /^[A-Za-z_$][\w$]*$/.test(local)) reExported.add(local);
627
+ }
628
+ }
629
+ for (const name of reExported) {
630
+ if (wrapped.has(name)) continue;
631
+ const declRe = new RegExp(`(^|[\\n;])\\s*(const|let|var)\\s+${escapeIdent(name)}\\s*=`);
632
+ const declMatch = declRe.exec(out);
633
+ if (!declMatch) continue;
634
+ wrapped.add(name);
635
+ if (declMatch[2] === 'const') {
636
+ out =
637
+ out.slice(0, declMatch.index) +
638
+ declMatch[0].replace(/\bconst\b/, 'let') +
639
+ out.slice(declMatch.index + declMatch[0].length);
640
+ }
641
+ }
642
+
643
+ for (const match of out.matchAll(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?/g)) {
644
+ const name = match[1]!;
645
+ if (wrapped.has(name)) continue;
646
+ const declRe = new RegExp(`(^|[\\n;])\\s*(const|let|var)\\s+${escapeIdent(name)}\\s*=`);
647
+ const declMatch = declRe.exec(out);
648
+ if (!declMatch) continue;
649
+ wrapped.add(name);
650
+ if (declMatch[2] === 'const') {
651
+ out =
652
+ out.slice(0, declMatch.index) +
653
+ declMatch[0].replace(/\bconst\b/, 'let') +
654
+ out.slice(declMatch.index + declMatch[0].length);
655
+ }
656
+ }
657
+ return out;
658
+ }
659
+
660
+ function rewriteObjectMethods(source: string, idBase: string): { source: string; wrapped: boolean } {
661
+ let out = source;
662
+ let wrapped = false;
663
+ let searchFrom = 0;
664
+ for (;;) {
665
+ const tail = out.slice(searchFrom);
666
+ const directive = directiveText.exec(tail);
667
+ if (!directive) break;
668
+ const index = searchFrom + directive.index;
669
+ if (!/\{\s*$/.test(out.slice(0, index))) {
670
+ searchFrom = index + directive[0].length;
671
+ continue;
672
+ }
673
+ const method = findObjectMethod(out, index);
674
+ if (!method) {
675
+ searchFrom = index + directive[0].length;
676
+ continue;
677
+ }
678
+ const id = `${idBase}#${method.enclosingName}.${method.name}`;
679
+ const captureList = method.captures.join(', ');
680
+ const wrappedHead = `${method.name}: ${boundHelper}(${JSON.stringify(id)}, ${method.asyncKeyword}function ${method.name}(${method.params}) {`;
681
+ out =
682
+ out.slice(0, method.declStart) +
683
+ wrappedHead +
684
+ out.slice(index + directive[0].length, method.bodyEnd) +
685
+ `}, () => [${captureList}])` +
686
+ out.slice(method.bodyEnd + 1);
687
+ wrapped = true;
688
+ searchFrom = method.declStart + wrappedHead.length;
689
+ }
690
+ return { source: out, wrapped };
691
+ }
692
+
693
+ function findObjectMethod(source: string, directiveIndex: number): NestedClosure | null {
694
+ const prefix = source.slice(0, directiveIndex);
695
+ const methodRe = /(?:^|[,{]\s*)(async\s+)?([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
696
+ let match: RegExpExecArray | null;
697
+ let best: { asyncKeyword: string; name: string; params: string; headStart: number; bodyOpen: number } | null =
698
+ null;
699
+ while ((match = methodRe.exec(prefix)) !== null) {
700
+ let beforeMatch = match.index - 1;
701
+ while (beforeMatch >= 0 && /\s/.test(source[beforeMatch]!)) beforeMatch -= 1;
702
+ if (source[beforeMatch] === '=') continue;
703
+ const kwOffset = match[0].search(/async|[A-Za-z_$]/);
704
+ const cleanStart = match.index + kwOffset;
705
+ const bodyOpen = match.index + match[0].length;
706
+ const between = source.slice(bodyOpen, directiveIndex);
707
+ let balance = 0;
708
+ for (const ch of between) {
709
+ if (ch === '{') balance += 1;
710
+ else if (ch === '}') balance -= 1;
711
+ }
712
+ if (balance === 0) {
713
+ best = {
714
+ asyncKeyword: match[1] ? 'async ' : '',
715
+ name: match[2]!,
716
+ params: match[3]!.trim(),
717
+ headStart: cleanStart,
718
+ bodyOpen,
719
+ };
720
+ }
721
+ }
722
+ if (!best) return null;
723
+ const enclosing = findEnclosingNamedFunction(source, best.headStart);
724
+ if (!enclosing) return null;
725
+ const bodyEnd = matchClosingBrace(source, best.bodyOpen);
726
+ if (bodyEnd < 0) return null;
727
+ const body = source.slice(best.bodyOpen, bodyEnd);
728
+ const methodParams = new Set(paramNames(best.params));
729
+ const captures = enclosing.params.filter(
730
+ param => !methodParams.has(param) && new RegExp(`\\b${escapeIdent(param)}\\b`).test(body),
731
+ );
732
+ return {
733
+ name: best.name,
734
+ params: best.params,
735
+ asyncKeyword: best.asyncKeyword,
736
+ declStart: best.headStart,
737
+ bodyEnd,
738
+ enclosingName: enclosing.name,
739
+ captures,
740
+ };
741
+ }
742
+
743
+ function rewriteStaticClassMethods(source: string, idBase: string): { source: string; wrapped: boolean } {
744
+ let out = source;
745
+ let wrapped = false;
746
+ let searchFrom = 0;
747
+ for (;;) {
748
+ const tail = out.slice(searchFrom);
749
+ const directive = directiveText.exec(tail);
750
+ if (!directive) break;
751
+ const index = searchFrom + directive.index;
752
+ if (!/\{\s*$/.test(out.slice(0, index))) {
753
+ searchFrom = index + directive[0].length;
754
+ continue;
755
+ }
756
+ const method = findStaticClassMethod(out, index);
757
+ if (!method) {
758
+ searchFrom = index + directive[0].length;
759
+ continue;
760
+ }
761
+ const id = `${idBase}#${method.className}.${method.name}`;
762
+ const wrappedHead = `static ${method.name} = __pnextUseCache(${JSON.stringify(id)}, ${method.asyncKeyword}function ${method.name}(${method.params}) {`;
763
+ out =
764
+ out.slice(0, method.declStart) +
765
+ wrappedHead +
766
+ out.slice(index + directive[0].length, method.bodyEnd) +
767
+ `})` +
768
+ out.slice(method.bodyEnd + 1);
769
+ wrapped = true;
770
+ searchFrom = method.declStart + wrappedHead.length;
771
+ }
772
+ return { source: out, wrapped };
773
+ }
774
+
775
+ function findStaticClassMethod(
776
+ source: string,
777
+ directiveIndex: number,
778
+ ): (NestedClosure & { className: string }) | null {
779
+ const prefix = source.slice(0, directiveIndex);
780
+ const methodRe = /(?:^|[\s;{}])static\s+(async\s+)?([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
781
+ let match: RegExpExecArray | null;
782
+ let best: { asyncKeyword: string; name: string; params: string; headStart: number; bodyOpen: number } | null =
783
+ null;
784
+ while ((match = methodRe.exec(prefix)) !== null) {
785
+ const kwOffset = match[0].indexOf('static');
786
+ const cleanStart = match.index + kwOffset;
787
+ const bodyOpen = match.index + match[0].length;
788
+ const between = source.slice(bodyOpen, directiveIndex);
789
+ let balance = 0;
790
+ for (const ch of between) {
791
+ if (ch === '{') balance += 1;
792
+ else if (ch === '}') balance -= 1;
793
+ }
794
+ if (balance === 0) {
795
+ best = {
796
+ asyncKeyword: match[1] ? 'async ' : '',
797
+ name: match[2]!,
798
+ params: match[3]!.trim(),
799
+ headStart: cleanStart,
800
+ bodyOpen,
801
+ };
802
+ }
803
+ }
804
+ if (!best) return null;
805
+ const className = findEnclosingClassName(source, best.headStart);
806
+ if (!className) return null;
807
+ const bodyEnd = matchClosingBrace(source, best.bodyOpen);
808
+ if (bodyEnd < 0) return null;
809
+ return {
810
+ name: best.name,
811
+ params: best.params,
812
+ asyncKeyword: best.asyncKeyword,
813
+ declStart: best.headStart,
814
+ bodyEnd,
815
+ enclosingName: className,
816
+ className,
817
+ captures: [],
818
+ };
819
+ }
820
+
821
+ function findEnclosingClassName(source: string, pos: number): string | null {
822
+ const before = source.slice(0, pos);
823
+ const re = /(?:^|[\s;{}])class\s+([A-Za-z_$][\w$]*)[^{]*\{/g;
824
+ let match: RegExpExecArray | null;
825
+ let candidate: string | null = null;
826
+ while ((match = re.exec(before)) !== null) {
827
+ const bodyOpen = match.index + match[0].length;
828
+ const close = matchClosingBrace(source, bodyOpen);
829
+ if (close > pos) candidate = match[1]!;
830
+ }
831
+ return candidate;
832
+ }
833
+
834
+ /**
835
+ * Function-level directives: for each `'use cache'` opening a function body,
836
+ * locate the enclosing declaration's name by scanning backwards, verify the
837
+ * region between the declaration head and the directive is a plausible
838
+ * function signature (brace balance of exactly one open), strip the directive
839
+ * and mark the name for wrapping.
840
+ */
841
+ function rewriteFunctionLevelDirectives(
842
+ source: string,
843
+ wrapped: Set<string>,
844
+ privateWrapped: Set<string>,
845
+ ): string {
846
+ let out = source;
847
+ let searchFrom = 0;
848
+ for (;;) {
849
+ const tail = out.slice(searchFrom);
850
+ const directive = directiveText.exec(tail);
851
+ if (!directive) break;
852
+ const index = searchFrom + directive.index;
853
+ const prefix = out.slice(0, index);
854
+
855
+ // Only a directive when it opens a function body; anything else is a plain
856
+ // string literal.
857
+ const declaration = opensFunctionBody(prefix) ? findEnclosingDeclaration(prefix) : null;
858
+ if (!declaration) {
859
+ searchFrom = index + directive[0].length;
860
+ continue;
861
+ }
862
+
863
+ let head = prefix;
864
+ if (declaration.constKeywordIndex !== undefined) {
865
+ head =
866
+ prefix.slice(0, declaration.constKeywordIndex) +
867
+ 'let ' +
868
+ prefix.slice(declaration.constKeywordIndex + 'const'.length);
869
+ }
870
+ wrapped.add(declaration.name);
871
+ if (directive[2] === 'private') privateWrapped.add(declaration.name);
872
+ out = head + out.slice(index + directive[0].length);
873
+ searchFrom = index;
874
+ }
875
+ return out;
876
+ }
877
+
878
+ /**
879
+ * Whether `prefix` ends at the start of a function body - the brace reached by skipping back over
880
+ * whitespace AND comments. A directive prologue is the first STATEMENT of the body, so a comment above
881
+ * `'use cache'` must not hide it. Scanned rather than matched: a regex alternating whitespace and
882
+ * comment forms backtracks badly.
883
+ */
884
+ function opensFunctionBody(prefix: string): boolean {
885
+ // The match is a comment's own text, not a directive, when `prefix` ends
886
+ // inside a comment — e.g. a line explaining `'use cache'` semantics.
887
+ if (prefix.includes('//', prefix.lastIndexOf('\n') + 1)) return false;
888
+ const blockOpen = prefix.lastIndexOf('/*');
889
+ if (blockOpen !== -1 && !prefix.includes('*/', blockOpen)) return false;
890
+
891
+ let end = prefix.length;
892
+ for (;;) {
893
+ end = prefix.slice(0, end).replace(/\s+$/, '').length;
894
+ if (end === 0) return false;
895
+ if (prefix.startsWith('*/', end - 2)) {
896
+ const open = prefix.lastIndexOf('/*', end - 2);
897
+ if (open === -1) return false;
898
+ end = open;
899
+ continue;
900
+ }
901
+ const comment = prefix.indexOf('//', prefix.lastIndexOf('\n', end - 1) + 1);
902
+ if (comment !== -1 && comment < end) {
903
+ end = comment;
904
+ continue;
905
+ }
906
+ return prefix[end - 1] === '{';
907
+ }
908
+ }
909
+
910
+ interface EnclosingDeclaration {
911
+ name: string;
912
+ /** Index of a `const` keyword to rewrite to `let`, when applicable. */
913
+ constKeywordIndex?: number;
914
+ }
915
+
916
+ function findEnclosingDeclaration(prefix: string): EnclosingDeclaration | null {
917
+ const candidates: { name: string; end: number; constKeywordIndex?: number }[] = [];
918
+
919
+ // Only top-level declarations (line starts at column 0): a nested closure's
920
+ // binding is function-scoped and cannot be reassigned from module level —
921
+ // those run uncached (the directive string is inert).
922
+ const topLevel = (index: number) => index === 0 || prefix[index - 1] === '\n';
923
+
924
+ const fnPattern = /(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)/g;
925
+ for (const match of prefix.matchAll(fnPattern)) {
926
+ if (match[1] && topLevel(match.index)) {
927
+ candidates.push({ name: match[1], end: match.index + match[0].length });
928
+ }
929
+ }
930
+ const bindingPattern = /(?:export\s+)?(const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g;
931
+ for (const match of prefix.matchAll(bindingPattern)) {
932
+ if (!match[2] || !topLevel(match.index)) continue;
933
+ candidates.push({
934
+ name: match[2],
935
+ end: match.index + match[0].length,
936
+ ...(match[1] === 'const'
937
+ ? { constKeywordIndex: match.index + match[0].indexOf('const') }
938
+ : {}),
939
+ });
940
+ }
941
+ if (candidates.length === 0) return null;
942
+ candidates.sort((a, b) => a.end - b.end);
943
+ const nearest = candidates[candidates.length - 1]!;
944
+
945
+ // The span between the declaration head and the directive must look like a
946
+ // signature followed by the opening body brace: exactly one unclosed '{'.
947
+ const span = prefix.slice(nearest.end);
948
+ let balance = 0;
949
+ for (const char of span) {
950
+ if (char === '{') balance += 1;
951
+ else if (char === '}') balance -= 1;
952
+ }
953
+ if (balance !== 1) return null;
954
+
955
+ return {
956
+ name: nearest.name,
957
+ ...(nearest.constKeywordIndex !== undefined
958
+ ? { constKeywordIndex: nearest.constKeywordIndex }
959
+ : {}),
960
+ };
961
+ }