@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,9 @@
1
+ function color(open: string, close: string) {
2
+ return (value: string) => (process.stdout.isTTY ? `${open}${value}${close}` : value);
3
+ }
4
+
5
+ /** Mid-gray for subordinate text. Readable on both light and dark terminals. */
6
+ export const dim = color('\x1b[38;5;244m', '\x1b[39m');
7
+ export const bold = color('\x1b[1m', '\x1b[22m');
8
+ export const cyan = color('\x1b[36m', '\x1b[39m');
9
+ export const green = color('\x1b[32m', '\x1b[39m');
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+
3
+ const contentTypes: Record<string, string> = {
4
+ '.avif': 'image/avif',
5
+ '.bmp': 'image/bmp',
6
+ '.css': 'text/css; charset=utf-8',
7
+ '.eot': 'application/vnd.ms-fontobject',
8
+ '.gif': 'image/gif',
9
+ '.htm': 'text/html; charset=utf-8',
10
+ '.html': 'text/html; charset=utf-8',
11
+ '.ico': 'image/x-icon',
12
+ '.jpeg': 'image/jpeg',
13
+ '.jpg': 'image/jpeg',
14
+ '.js': 'text/javascript; charset=utf-8',
15
+ '.json': 'application/json; charset=utf-8',
16
+ '.mjs': 'text/javascript; charset=utf-8',
17
+ '.otf': 'font/otf',
18
+ '.png': 'image/png',
19
+ '.svg': 'image/svg+xml',
20
+ '.tif': 'image/tiff',
21
+ '.tiff': 'image/tiff',
22
+ '.ttf': 'font/ttf',
23
+ '.wasm': 'application/wasm',
24
+ '.webp': 'image/webp',
25
+ '.woff': 'font/woff',
26
+ '.woff2': 'font/woff2',
27
+ };
28
+
29
+ export function contentType(file: string): string {
30
+ return contentTypes[path.extname(file).toLowerCase()] ?? 'application/octet-stream';
31
+ }
@@ -0,0 +1,7 @@
1
+ export function safeDecode(value: string) {
2
+ try {
3
+ return decodeURIComponent(value);
4
+ } catch {
5
+ return value;
6
+ }
7
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Buffered sink for PNEXT_DEV_PROFILE lines. A warm request emits many phase lines, and writing each
3
+ * straight to stdout inflated every enclosing span it sat inside - the measurement changed what it
4
+ * measured. Lines accumulate here and the request handler flushes them in one write.
5
+ */
6
+ const SINK = Symbol.for('pnext.devProfileSink');
7
+
8
+ function sink(): string[] {
9
+ const store = globalThis as Record<PropertyKey, unknown>;
10
+ return (store[SINK] ??= []) as string[];
11
+ }
12
+
13
+ export function recordDevProfileLine(line: string) {
14
+ sink().push(line);
15
+ }
16
+
17
+ /** Emit and clear everything buffered so far (one write per request). */
18
+ export function flushDevProfileLines() {
19
+ const lines = sink();
20
+ if (lines.length === 0) return;
21
+ const out = lines.join('\n');
22
+ lines.length = 0;
23
+ console.log(out);
24
+ }
25
+
26
+ export function formatProfileDuration(durationMs: number) {
27
+ const ms = Math.max(0, durationMs);
28
+ if (ms < 10) return `${ms.toFixed(2)}ms`;
29
+ if (ms < 1000) return `${ms.toFixed(1)}ms`;
30
+ return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`;
31
+ }
@@ -0,0 +1,29 @@
1
+ // Once-per-error server log guard. An SSR error can reach several log sites in one request (the renderer's
2
+ // inline report, then the top-level start/dev-server catch). Tag the error object the first time it is
3
+ // logged so the downstream catch skips its own `console.error` and each error surfaces once.
4
+ //
5
+ // The tag lives on the error itself (a non-enumerable symbol property), so it survives across log sites
6
+ // without a WeakSet keyed to a work unit - an error thrown outside any work-unit scope is still deduped. A
7
+ // non-Error throw cannot carry the tag; those are always logged.
8
+
9
+ const ERROR_LOGGED = Symbol.for('pnext.errorLogged');
10
+
11
+ /**
12
+ * Mark `error` as logged. Returns true when this call was the FIRST to mark it (the caller should log), false
13
+ * when it was already marked (skip the log). Non-object throws always return true - they cannot be tagged, so
14
+ * they are never deduped.
15
+ */
16
+ export function markErrorLogged(error: unknown): boolean {
17
+ if (error === null || (typeof error !== 'object' && typeof error !== 'function')) {
18
+ return true;
19
+ }
20
+ const holder = error as Record<symbol, unknown>;
21
+ if (holder[ERROR_LOGGED]) return false;
22
+ Object.defineProperty(error, ERROR_LOGGED, {
23
+ value: true,
24
+ enumerable: false,
25
+ configurable: true,
26
+ writable: true,
27
+ });
28
+ return true;
29
+ }
@@ -0,0 +1,31 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
5
+
6
+ // One cached dirent listing per directory replaces per-file stat probes.
7
+ // node_modules only: installed layouts are process-immutable, app paths are not.
8
+ const dirents = new Map<string, Set<string> | undefined>();
9
+
10
+ function direntsFor(dir: string) {
11
+ if (dirents.has(dir)) return dirents.get(dir);
12
+ let names: Set<string> | undefined;
13
+ try {
14
+ names = new Set(readdirSync(dir));
15
+ } catch {
16
+ names = undefined;
17
+ }
18
+ dirents.set(dir, names);
19
+ return names;
20
+ }
21
+
22
+ /** `existsSync`, served from the dirent cache for node_modules paths. */
23
+ export function cachedExistsSync(file: string): boolean {
24
+ if (!file.includes(NODE_MODULES)) return existsSync(file);
25
+ return direntsFor(path.dirname(file))?.has(path.basename(file)) ?? false;
26
+ }
27
+
28
+ /** Structural-save invalidation: a file added or deleted changes the answers. */
29
+ export function clearNodeModulesFsCache(): void {
30
+ dirents.clear();
31
+ }
@@ -0,0 +1,119 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { mkdir, readdir, rename, rm, stat, readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ // Write-then-rename so a concurrent import never observes a truncated file; Bun caches a failed load for the
6
+ // life of the process. The temp name must be unique per CALL, not per process: two writers of the same output
7
+ // (two routes demanding one vendor bundle, a re-write after a cache eviction) otherwise share a name and the
8
+ // second rename hits ENOENT once the first moved it away. Losing that race is harmless - both wrote the same
9
+ // content-keyed bytes - so a rename whose temp is gone only fails if the destination is missing too.
10
+ let atomicWriteSequence = 0;
11
+ export async function writeFileAtomic(file: string, contents: string) {
12
+ const temp = `${file}.${process.pid.toString(36)}.${(++atomicWriteSequence).toString(36)}.tmp`;
13
+ await writeFile(temp, contents);
14
+ try {
15
+ await rename(temp, file);
16
+ } catch (error) {
17
+ if (!existsSync(file)) throw error;
18
+ }
19
+ }
20
+
21
+ export async function listFiles(root: string): Promise<string[]> {
22
+ if (!existsSync(root)) return [];
23
+ const entries = await readdir(root, { withFileTypes: true });
24
+ // Subdirectories are walked concurrently — a depth-first await chain spends
25
+ // the whole walk waiting on one readdir at a time (0.6 s on a ~1k-file app).
26
+ // Results stay in readdir order so callers keep a stable file order.
27
+ const branches = await Promise.all(
28
+ entries.map(entry => {
29
+ const entryPath = path.join(root, entry.name);
30
+ if (entry.isDirectory()) return listFiles(entryPath);
31
+ return Promise.resolve(entry.isFile() ? [entryPath] : []);
32
+ }),
33
+ );
34
+ return branches.flat();
35
+ }
36
+
37
+ interface DirListing {
38
+ files: Set<string>;
39
+ dirs: string[];
40
+ }
41
+
42
+ let dirListings: Map<string, DirListing> | undefined;
43
+
44
+ /**
45
+ * Memoize directory listings for the duration of `run`. A route scan asks the
46
+ * same handful of directories for every convention file of every route, and the
47
+ * answers cannot change mid-scan. Outside a `withDirCache` scope every lookup
48
+ * reads the directory fresh, so request-time callers never see a stale tree.
49
+ */
50
+ export function withDirCache<T>(run: () => T): T {
51
+ if (dirListings) return run();
52
+ dirListings = new Map();
53
+ try {
54
+ return run();
55
+ } finally {
56
+ dirListings = undefined;
57
+ }
58
+ }
59
+
60
+ /** File and subdirectory names of `dir` (both empty when it does not exist). */
61
+ export function readDirListing(dir: string): DirListing {
62
+ const cached = dirListings?.get(dir);
63
+ if (cached) return cached;
64
+ let listing: DirListing;
65
+ try {
66
+ const entries = readdirSync(dir, { withFileTypes: true });
67
+ listing = {
68
+ files: new Set(entries.filter(entry => !entry.isDirectory()).map(entry => entry.name)),
69
+ dirs: entries
70
+ .filter(entry => entry.isDirectory())
71
+ .map(entry => entry.name)
72
+ .sort(),
73
+ };
74
+ } catch {
75
+ listing = { files: new Set(), dirs: [] };
76
+ }
77
+ dirListings?.set(dir, listing);
78
+ return listing;
79
+ }
80
+
81
+ /** listFiles for callers already running synchronously (route fact scans). */
82
+ export function listFilesSync(root: string): string[] {
83
+ if (!existsSync(root)) return [];
84
+ const out: string[] = [];
85
+
86
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
87
+ const entryPath = path.join(root, entry.name);
88
+ if (entry.isDirectory()) out.push(...listFilesSync(entryPath));
89
+ else if (entry.isFile()) out.push(entryPath);
90
+ }
91
+
92
+ return out;
93
+ }
94
+
95
+ export async function ensureEmptyDir(dir: string) {
96
+ await rm(dir, { recursive: true, force: true });
97
+ await mkdir(dir, { recursive: true });
98
+ }
99
+
100
+ export async function ensureDir(dir: string) {
101
+ await mkdir(dir, { recursive: true });
102
+ }
103
+
104
+ export async function writeText(file: string, value: string) {
105
+ await ensureDir(path.dirname(file));
106
+ await writeFile(file, value);
107
+ }
108
+
109
+ export async function readText(file: string) {
110
+ return readFile(file, 'utf8');
111
+ }
112
+
113
+ export async function fileMtime(file: string) {
114
+ return (await stat(file)).mtimeMs;
115
+ }
116
+
117
+ export function toPosixPath(value: string) {
118
+ return value.split(path.sep).join('/');
119
+ }
@@ -0,0 +1,46 @@
1
+ export function escapeHtml(value: unknown) {
2
+ return stringifyHtmlValue(value).replace(/[&<>"']/g, char => {
3
+ switch (char) {
4
+ case '&':
5
+ return '&amp;';
6
+ case '<':
7
+ return '&lt;';
8
+ case '>':
9
+ return '&gt;';
10
+ case '"':
11
+ return '&quot;';
12
+ case "'":
13
+ return '&#39;';
14
+ default:
15
+ return char;
16
+ }
17
+ });
18
+ }
19
+
20
+ export function stringifyHtmlValue(value: unknown) {
21
+ if (value === undefined || value === null) return '';
22
+ if (typeof value === 'string') return value;
23
+ if (
24
+ typeof value === 'number' ||
25
+ typeof value === 'boolean' ||
26
+ typeof value === 'bigint'
27
+ ) {
28
+ return String(value);
29
+ }
30
+ if (value instanceof URL) return value.href;
31
+ if (value instanceof Date) return value.toISOString();
32
+ if (typeof value !== 'object') return '';
33
+
34
+ const stringifier = (value as { toString?: (this: object) => unknown }).toString;
35
+ if (!stringifier || stringifier === Object.prototype.toString) return '';
36
+ const stringified = stringifier.call(value);
37
+ if (typeof stringified === 'string') return stringified;
38
+ if (
39
+ typeof stringified === 'number' ||
40
+ typeof stringified === 'boolean' ||
41
+ typeof stringified === 'bigint'
42
+ ) {
43
+ return String(stringified);
44
+ }
45
+ return '';
46
+ }
@@ -0,0 +1,378 @@
1
+ // Typed-array / ArrayBuffer wire markers. Binary values are not JSON-native, so
2
+ // serializeProps marker-encodes them (base64 payload, same $$pnext_* convention
3
+ // as the action wire's $$pnext_date) instead of throwing; the client/server
4
+ // revive the marker back into the original view. assertSerializable accepts
5
+ // them (they are "serializable" via the marker path) rather than rejecting.
6
+ const TYPED_ARRAY_MARKER = '$$pnext_typed_array';
7
+ const ARRAY_BUFFER_MARKER = '$$pnext_array_buffer';
8
+ // Map/Set are part of React's flight wire vocabulary (client components can receive them as props); pnext
9
+ // does not stream them as distinct RSC row types yet, so they ride as ordinary marker objects, entries
10
+ // encoded recursively like the binary markers above, instead of throwing "must be a plain object". Client
11
+ // code that only round-trips the value sees a marker object rather than a live Map/Set instance - a known
12
+ // gap, not a full flight-protocol Map/Set.
13
+ const MAP_MARKER = '$$pnext_map';
14
+ const SET_MARKER = '$$pnext_set';
15
+ // Cyclic props (a client component receiving `obj.self === obj`) are part of React's flight vocabulary:
16
+ // flight emits a BACK-REFERENCE to the row/path the value was first written at rather than expanding it
17
+ // forever. Same idea here, scoped to the ancestor chain: an object that reappears INSIDE ITSELF is written as
18
+ // `{$$pnext_ref: [...path from the props root]}` and the client patches the original object back in,
19
+ // restoring identity. Only true cycles are encoded this way - a value that merely appears twice in different
20
+ // branches is still expanded, so every acyclic payload is byte-identical to what it was before.
21
+ const REF_MARKER = '$$pnext_ref';
22
+
23
+ type TypedArray =
24
+ | Int8Array
25
+ | Uint8Array
26
+ | Uint8ClampedArray
27
+ | Int16Array
28
+ | Uint16Array
29
+ | Int32Array
30
+ | Uint32Array
31
+ | Float32Array
32
+ | Float64Array
33
+ | BigInt64Array
34
+ | BigUint64Array;
35
+
36
+ function isTypedArray(value: unknown): value is TypedArray {
37
+ return ArrayBuffer.isView(value) && !(value instanceof DataView);
38
+ }
39
+
40
+ function bytesToBase64(bytes: Uint8Array): string {
41
+ if (typeof Buffer !== 'undefined') return Buffer.from(bytes).toString('base64');
42
+ let binary = '';
43
+ for (const byte of bytes) binary += String.fromCharCode(byte);
44
+ return btoa(binary);
45
+ }
46
+
47
+ function encodeTypedArray(value: TypedArray) {
48
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
49
+ return { [TYPED_ARRAY_MARKER]: { type: value.constructor.name, data: bytesToBase64(bytes) } };
50
+ }
51
+
52
+ function encodeArrayBuffer(value: ArrayBuffer) {
53
+ return { [ARRAY_BUFFER_MARKER]: bytesToBase64(new Uint8Array(value)) };
54
+ }
55
+
56
+ /**
57
+ * Deep-copy `value`, replacing typed arrays / ArrayBuffers with wire markers
58
+ * and self-references (cycles) with `$$pnext_ref` back-references.
59
+ *
60
+ * `ancestors` maps every object on the CURRENT path to the path it was written
61
+ * at; it is unwound on the way back out, so only a value that contains itself
62
+ * becomes a reference.
63
+ */
64
+ function encodeBinary(
65
+ value: unknown,
66
+ ancestors = new Map<object, string[]>(),
67
+ path: string[] = [],
68
+ ): unknown {
69
+ if (value === null || typeof value !== 'object') return value;
70
+ if (isTypedArray(value)) return encodeTypedArray(value);
71
+ if (value instanceof ArrayBuffer) return encodeArrayBuffer(value);
72
+ const ancestorPath = ancestors.get(value);
73
+ if (ancestorPath) return { [REF_MARKER]: ancestorPath };
74
+ ancestors.set(value, path);
75
+ const encoded = encodeContainer(value, ancestors, path);
76
+ ancestors.delete(value);
77
+ return encoded;
78
+ }
79
+
80
+ function encodeContainer(
81
+ value: object,
82
+ ancestors: Map<object, string[]>,
83
+ path: string[],
84
+ ): unknown {
85
+ if (Array.isArray(value)) {
86
+ return value.map((item, index) => encodeBinary(item, ancestors, [...path, String(index)]));
87
+ }
88
+ if (value instanceof Map) {
89
+ return {
90
+ [MAP_MARKER]: [...value.entries()].map(([k, v]) => [
91
+ encodeBinary(k, ancestors),
92
+ encodeBinary(v, ancestors),
93
+ ]),
94
+ };
95
+ }
96
+ if (value instanceof Set) {
97
+ return { [SET_MARKER]: [...value].map(item => encodeBinary(item, ancestors)) };
98
+ }
99
+ if (Object.getPrototypeOf(value) !== Object.prototype) return value;
100
+ const out: Record<string, unknown> = {};
101
+ for (const [key, item] of Object.entries(value)) {
102
+ out[key] = encodeBinary(item, ancestors, [...path, key]);
103
+ }
104
+ return out;
105
+ }
106
+
107
+ // Errors reach `error.tsx` as props and ride the same marker convention (the
108
+ // encoder for this one lives on the render side, with the boundary props).
109
+ const ERROR_MARKER = '$$pnextError';
110
+
111
+ /**
112
+ * Revive the wire markers `serializeProps` wrote, IN PLACE:
113
+ *
114
+ * - `$$pnext_ref` back-references, restoring the identity the server saw. Paths are relative to the root
115
+ * passed in - the same root serializeProps encoded - and always point at an ancestor, so the target is
116
+ * already materialized when the marker is hit.
117
+ * - typed arrays / ArrayBuffers, back into the original view.
118
+ * - Errors, via `extra` - see `reviveSerializedErrorRefs`.
119
+ *
120
+ * The action runtime revives all of these for apps that have it, but an app with no server actions never
121
+ * loads it - and its islands would then hydrate with marker objects where the server rendered real values.
122
+ */
123
+ export function reviveSerializedRefs<T>(
124
+ root: T,
125
+ extra?: (node: Record<string, unknown>) => unknown,
126
+ ): T {
127
+ if (root === null || typeof root !== 'object') return root;
128
+ const visit = (node: Record<string, unknown>): void => {
129
+ for (const key of Object.keys(node)) {
130
+ const item = node[key];
131
+ if (item === null || typeof item !== 'object' || ArrayBuffer.isView(item)) continue;
132
+ const child = item as Record<string, unknown>;
133
+ const revived = refTarget(root, child) ?? extra?.(child) ?? binaryTarget(child);
134
+ if (revived !== undefined) {
135
+ node[key] = revived;
136
+ continue;
137
+ }
138
+ visit(child);
139
+ }
140
+ };
141
+ visit(root as Record<string, unknown>);
142
+ return root;
143
+ }
144
+
145
+ /**
146
+ * The above plus `$$pnextError`. Kept as its own export so the error branch
147
+ * tree-shakes out of core/react client entries: `$$pnextError` is a
148
+ * next-compat-only marker (the renderer's `serializeActionProps` returns props
149
+ * untouched when compat is off), and only compat entries import this name.
150
+ */
151
+ export function reviveSerializedErrorRefs<T>(root: T): T {
152
+ return reviveSerializedRefs(root, errorTarget);
153
+ }
154
+
155
+ function errorTarget(node: Record<string, unknown>): Error | undefined {
156
+ const marker = node[ERROR_MARKER];
157
+ if (marker === null || typeof marker !== 'object') return undefined;
158
+ const data = marker as { name?: unknown; message?: unknown; digest?: unknown };
159
+ const error = new Error(typeof data.message === 'string' ? data.message : '');
160
+ if (typeof data.name === 'string') error.name = data.name;
161
+ if (typeof data.digest === 'string') (error as Error & { digest?: string }).digest = data.digest;
162
+ return error;
163
+ }
164
+
165
+ // The wire carries the view's constructor NAME. Resolved off the global rather than a name-to-constructor
166
+ // table (the table is first-paint client code): `BYTES_PER_ELEMENT` exists only on typed-array constructors,
167
+ // so any other name - including one that resolves to something else entirely - resolves to undefined and the
168
+ // marker is left alone.
169
+ function typedArrayConstructor(name: string) {
170
+ const constructor = (globalThis as unknown as Record<string, unknown>)[name];
171
+ return typeof constructor === 'function' && 'BYTES_PER_ELEMENT' in constructor
172
+ ? (constructor as unknown as new (buffer: ArrayBuffer) => TypedArray)
173
+ : undefined;
174
+ }
175
+
176
+ // Decode side is client-only (the encoder is what runs on the server), so no
177
+ // Buffer fast path — `atob` is global in every runtime that reaches this.
178
+ function base64ToBytes(data: string): Uint8Array {
179
+ return Uint8Array.from(atob(data), character => character.charCodeAt(0));
180
+ }
181
+
182
+ function binaryTarget(node: Record<string, unknown>): TypedArray | ArrayBuffer | undefined {
183
+ const buffer = node[ARRAY_BUFFER_MARKER];
184
+ if (typeof buffer === 'string') return base64ToBytes(buffer).buffer as ArrayBuffer;
185
+ const typed = node[TYPED_ARRAY_MARKER];
186
+ if (typed === null || typeof typed !== 'object') return undefined;
187
+ const { type, data } = typed as { type?: unknown; data?: unknown };
188
+ if (typeof type !== 'string' || typeof data !== 'string') return undefined;
189
+ const Constructor = typedArrayConstructor(type);
190
+ return Constructor ? new Constructor(base64ToBytes(data).buffer as ArrayBuffer) : undefined;
191
+ }
192
+
193
+ function refTarget(root: unknown, node: Record<string, unknown>): unknown {
194
+ const path = node[REF_MARKER];
195
+ if (!Array.isArray(path)) return undefined;
196
+ let current: unknown = root;
197
+ for (const key of path) {
198
+ if (current === null || typeof current !== 'object') return undefined;
199
+ current = (current as Record<string, unknown>)[String(key)];
200
+ }
201
+ return current;
202
+ }
203
+
204
+ /** A preact vnode: `h` sets `constructor` to undefined on every one it makes. */
205
+ export function isElementLike(value: unknown): boolean {
206
+ if (value === null || typeof value !== 'object') return false;
207
+ return (
208
+ (value as { constructor?: unknown }).constructor === undefined &&
209
+ 'type' in value &&
210
+ 'props' in value
211
+ );
212
+ }
213
+
214
+ export function assertSerializable(
215
+ value: unknown,
216
+ path = 'props',
217
+ seen = new Set<unknown>(),
218
+ // Island props only: element values are replaced by `$$pnext_slot` markers upstream (islands/
219
+ // static-slots), so they are serializable there. Server-action args/returns keep throwing.
220
+ allowElements = false,
221
+ ) {
222
+ if (value == null) return;
223
+ if (allowElements && isElementLike(value)) return;
224
+ const type = typeof value;
225
+ if (type === 'string' || type === 'number' || type === 'boolean') return;
226
+ if (type === 'function' || type === 'symbol' || type === 'bigint' || type === 'undefined') {
227
+ throw new Error(`${path} is not serializable: ${type}`);
228
+ }
229
+ // Typed arrays / ArrayBuffers are serializable via the binary marker path.
230
+ if (isTypedArray(value) || value instanceof ArrayBuffer) return;
231
+ if (value instanceof Date || value instanceof URL) {
232
+ throw new Error(`${path} must be a plain JSON value`);
233
+ }
234
+ if (typeof value !== 'object') return;
235
+ // A value that contains ITSELF is serializable: encodeBinary writes it as a
236
+ // `$$pnext_ref` back-reference (React flight does the same), so stop walking
237
+ // rather than rejecting the props.
238
+ if (seen.has(value)) return;
239
+ seen.add(value);
240
+
241
+ if (Array.isArray(value)) {
242
+ value.forEach((item, index) =>
243
+ assertSerializable(item, `${path}[${index}]`, seen, allowElements),
244
+ );
245
+ return;
246
+ }
247
+
248
+ // The slot walk (islands/static-slots) never enters Map/Set, so elements inside them must
249
+ // still throw rather than ship mangled.
250
+ if (value instanceof Map) {
251
+ let index = 0;
252
+ for (const [key, item] of value.entries()) {
253
+ assertSerializable(key, `${path}<Map key ${index}>`, seen);
254
+ assertSerializable(item, `${path}<Map value ${index}>`, seen);
255
+ index++;
256
+ }
257
+ return;
258
+ }
259
+
260
+ if (value instanceof Set) {
261
+ let index = 0;
262
+ for (const item of value) {
263
+ assertSerializable(item, `${path}<Set item ${index}>`, seen);
264
+ index++;
265
+ }
266
+ return;
267
+ }
268
+
269
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
270
+ throw new Error(`${path} must be a plain object`);
271
+ }
272
+
273
+ for (const [key, item] of Object.entries(value)) {
274
+ assertSerializable(item, `${path}.${key}`, seen, allowElements);
275
+ }
276
+ }
277
+
278
+ // ---------------------------------------------------------------------------
279
+ // Tainted object references (COMPAT `experimental.taint` opts in; the registry
280
+ // itself is inert until something registers a value).
281
+ //
282
+ // React's taint APIs (taintObjectReference) don't exist under preact, so the
283
+ // client-props serializer enforces the same contract with an IDENTITY check:
284
+ // compat registers `process.env` when `experimental.taint` is set, and any
285
+ // client-component prop that IS that object (at any depth) throws the
286
+ // registered message. The throw travels the normal render-error path, so dev
287
+ // shows the message in the nearest error.tsx boundary and prod shows React's
288
+ // redacted #441 text.
289
+ //
290
+ // The registry is anchored on globalThis: a module loaded twice (built copy vs
291
+ // original) must not split the tainted set.
292
+ // ---------------------------------------------------------------------------
293
+
294
+ const TAINT_REGISTRY_KEY = '__pnext_tainted_values__';
295
+
296
+ /** Dev message plus the redacted text prod shows in its place. */
297
+ export interface TaintMessages {
298
+ dev: string;
299
+ prod: string;
300
+ }
301
+
302
+ function taintRegistry(): Map<object, TaintMessages> {
303
+ const host = globalThis as { [TAINT_REGISTRY_KEY]?: Map<object, TaintMessages> };
304
+ const existing = host[TAINT_REGISTRY_KEY];
305
+ if (existing) return existing;
306
+ const created = new Map<object, TaintMessages>();
307
+ host[TAINT_REGISTRY_KEY] = created;
308
+ return created;
309
+ }
310
+
311
+ /** Register `value` as unsafe to pass to a client component. */
312
+ export function taintObjectReference(messages: TaintMessages, value: object): void {
313
+ taintRegistry().set(value, messages);
314
+ }
315
+
316
+ /**
317
+ * Throw if a tainted object reference appears anywhere in `value`. Must run on
318
+ * the RAW props (before any serializer copies plain objects and drops the
319
+ * identity the check relies on).
320
+ */
321
+ export function assertNotTainted(value: unknown, seen = new Set<unknown>()): void {
322
+ const registry = taintRegistry();
323
+ if (registry.size === 0) return;
324
+ if (value === null || typeof value !== 'object') return;
325
+ const messages = registry.get(value);
326
+ if (messages !== undefined) {
327
+ // Next surfaces the explanatory message in dev only; prod renders React's
328
+ // redacted RSC error text in the nearest error boundary.
329
+ throw new Error(
330
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
331
+ process.env.NODE_ENV === 'development' ? messages.dev : messages.prod,
332
+ );
333
+ }
334
+ if (seen.has(value)) return;
335
+ seen.add(value);
336
+ if (Array.isArray(value)) {
337
+ for (const item of value) assertNotTainted(item, seen);
338
+ return;
339
+ }
340
+ if (value instanceof Map) {
341
+ for (const [key, item] of value.entries()) {
342
+ assertNotTainted(key, seen);
343
+ assertNotTainted(item, seen);
344
+ }
345
+ return;
346
+ }
347
+ if (value instanceof Set) {
348
+ for (const item of value) assertNotTainted(item, seen);
349
+ return;
350
+ }
351
+ const proto = Object.getPrototypeOf(value) as object | null;
352
+ if (proto !== Object.prototype && proto !== null) return;
353
+ for (const item of Object.values(value)) assertNotTainted(item, seen);
354
+ }
355
+
356
+ export function serializeProps(value: unknown, options?: { allowElements?: boolean }) {
357
+ assertNotTainted(value);
358
+ assertSerializable(value, 'props', new Set(), options?.allowElements);
359
+ return JSON.stringify(encodeBinary(value)).replace(
360
+ /[<>&\u2028\u2029]/g,
361
+ char => {
362
+ switch (char) {
363
+ case '<':
364
+ return '\\u003c';
365
+ case '>':
366
+ return '\\u003e';
367
+ case '&':
368
+ return '\\u0026';
369
+ case '\u2028':
370
+ return '\\u2028';
371
+ case '\u2029':
372
+ return '\\u2029';
373
+ default:
374
+ return char;
375
+ }
376
+ },
377
+ );
378
+ }