@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,51 @@
1
+ // `next/offline` - the useOffline() hook. Reports whether the app is currently offline: false on the
2
+ // server and during hydration; in the browser it tracks the native online/offline events. The router's
3
+ // own offline handling (soft-nav retry on reconnect) lives in api/router.ts; this hook is only the
4
+ // UI-facing subscription.
5
+
6
+ import { useEffect, useState } from 'preact/hooks';
7
+
8
+ type Listener = () => void;
9
+
10
+ const listeners = new Set<Listener>();
11
+ let installed = false;
12
+ let offline = false;
13
+
14
+ function currentOffline(): boolean {
15
+ if (typeof navigator === 'undefined') return false;
16
+ return navigator.onLine === false;
17
+ }
18
+
19
+ function install() {
20
+ if (installed || (!process.browser && typeof window === 'undefined')) return;
21
+ installed = true;
22
+ offline = currentOffline();
23
+ const update = () => {
24
+ const next = currentOffline();
25
+ if (next === offline) return;
26
+ offline = next;
27
+ for (const listener of [...listeners]) listener();
28
+ };
29
+ window.addEventListener('offline', update);
30
+ window.addEventListener('online', update);
31
+ }
32
+
33
+ /**
34
+ * True while the browser reports no connectivity. Server-rendered and
35
+ * hydrating trees always see `false` (matching Next: the offline state is a
36
+ * client-side signal only).
37
+ */
38
+ export function useOffline(): boolean {
39
+ const [value, setValue] = useState(false);
40
+ useEffect(() => {
41
+ install();
42
+ const listener = () => setValue(offline);
43
+ listeners.add(listener);
44
+ // The state may have flipped between render and subscription.
45
+ listener();
46
+ return () => {
47
+ listeners.delete(listener);
48
+ };
49
+ }, []);
50
+ return value;
51
+ }
@@ -0,0 +1,324 @@
1
+ // next/og - ImageResponse (COMPAT).
2
+ //
3
+ // Renders a JSX element to a PNG the way Next's `next/og` / `@vercel/og` does: satori compiles the
4
+ // element tree to an SVG, then @resvg/resvg-js rasterizes it to PNG bytes. Both are optionalDependencies
5
+ // - when either is absent (or no usable font is available for satori's text layout) the endpoint falls
6
+ // back to a self-contained minimal-but-valid PNG, so it still answers `content-type: image/png` with a
7
+ // non-empty body in every runtime.
8
+ //
9
+ // The Response is constructed synchronously (Response bodies may be a stream), so the async
10
+ // satori/resvg pipeline is driven through a ReadableStream that resolves the bytes lazily; the fallback
11
+ // path also flows through the stream.
12
+
13
+ import { createRequire } from 'node:module';
14
+ import path from 'node:path';
15
+
16
+ export interface ImageResponseFont {
17
+ name: string;
18
+ data: ArrayBuffer | Uint8Array;
19
+ weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
20
+ style?: 'normal' | 'italic';
21
+ }
22
+
23
+ export type ImageResponseOptions = ResponseInit & {
24
+ width?: number;
25
+ height?: number;
26
+ debug?: boolean;
27
+ fonts?: ImageResponseFont[];
28
+ emoji?: string;
29
+ };
30
+
31
+ const DEFAULT_WIDTH = 1200;
32
+ const DEFAULT_HEIGHT = 630;
33
+ const MAX_DIMENSION = 4096;
34
+ const MAX_RAW_IMAGE_BYTES = 3 * 1024 * 1024;
35
+ const PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
36
+
37
+ let crcTable: Uint32Array | undefined;
38
+
39
+ export class ImageResponse extends Response {
40
+ static displayName = 'ImageResponse';
41
+
42
+ constructor(element: unknown, options: ImageResponseOptions = {}) {
43
+ // Validate dimensions eagerly so an invalid width/height throws from the
44
+ // constructor (matching Next), not later inside the async PNG stream — a
45
+ // stream-time RangeError would otherwise surface as a degraded fallback PNG
46
+ // instead of the synchronous error callers assert on.
47
+ dimension(options.width, DEFAULT_WIDTH, 'width');
48
+ dimension(options.height, DEFAULT_HEIGHT, 'height');
49
+ const headers = new Headers(options.headers);
50
+ headers.set('content-type', 'image/png');
51
+ // Match Next's default caching behavior for on-the-fly OG images.
52
+ if (!headers.has('cache-control')) {
53
+ headers.set(
54
+ 'cache-control',
55
+ 'public, immutable, no-transform, max-age=31536000',
56
+ );
57
+ }
58
+ super(pngStream(element, options), {
59
+ status: options.status,
60
+ statusText: options.statusText,
61
+ headers,
62
+ });
63
+ }
64
+ }
65
+
66
+ /**
67
+ * A one-shot ReadableStream that renders the element to PNG bytes. Rendering is
68
+ * async (satori/resvg), so we enqueue inside `start`. Any failure degrades to
69
+ * the deterministic minimal PNG rather than erroring the response.
70
+ */
71
+ function pngStream(element: unknown, options: ImageResponseOptions): ReadableStream<Uint8Array> {
72
+ return new ReadableStream<Uint8Array>({
73
+ async start(controller) {
74
+ let bytes: Uint8Array;
75
+ try {
76
+ bytes =
77
+ (await renderWithVercelOg(element, options)) ??
78
+ (await renderWithSatori(element, options)) ??
79
+ createFallbackPng(options);
80
+ } catch {
81
+ bytes = createFallbackPng(options);
82
+ }
83
+ controller.enqueue(bytes);
84
+ controller.close();
85
+ },
86
+ });
87
+ }
88
+
89
+ interface VercelOgModule {
90
+ ImageResponse: new (element: unknown, options?: Record<string, unknown>) => Response;
91
+ }
92
+
93
+ /**
94
+ * Preferred path: delegate to the app's own `@vercel/og` (which `next/og` wraps in real Next). It
95
+ * bundles satori, a wasm resvg and a default font, so it produces a real JSX-to-PNG rasterization even
96
+ * when the fixture provides no `fonts` option - satori alone cannot lay out text without one.
97
+ *
98
+ * Resolved from the APP ROOT, not from pnext's own node_modules: the bundled server output lives outside
99
+ * the app tree, so a bare `import('@vercel/og')` would resolve against the wrong node_modules, or not at
100
+ * all. Resolving to an absolute path first keeps the optional dep bound to the user's install.
101
+ */
102
+ async function renderWithVercelOg(
103
+ element: unknown,
104
+ options: ImageResponseOptions,
105
+ ): Promise<Uint8Array | undefined> {
106
+ const og = await loadFromAppRoot<VercelOgModule>('@vercel/og');
107
+ if (!og?.ImageResponse) return undefined;
108
+
109
+ const width = dimension(options.width, DEFAULT_WIDTH, 'width');
110
+ const height = dimension(options.height, DEFAULT_HEIGHT, 'height');
111
+ const response = new og.ImageResponse(element, {
112
+ width,
113
+ height,
114
+ ...(options.fonts === undefined ? {} : { fonts: options.fonts }),
115
+ ...(options.debug === undefined ? {} : { debug: options.debug }),
116
+ ...(options.emoji === undefined ? {} : { emoji: options.emoji }),
117
+ });
118
+ const bytes = new Uint8Array(await response.arrayBuffer());
119
+ return bytes.length > 0 ? bytes : undefined;
120
+ }
121
+
122
+ interface SatoriModule {
123
+ default: (element: unknown, opts: Record<string, unknown>) => Promise<string>;
124
+ }
125
+ interface ResvgModule {
126
+ Resvg: new (
127
+ svg: string | Uint8Array,
128
+ opts?: Record<string, unknown>,
129
+ ) => { render: () => { asPng: () => Uint8Array } };
130
+ }
131
+
132
+ /**
133
+ * Full JSX-to-PNG pipeline via the optional deps. Returns undefined - triggering the fallback - when
134
+ * either dependency or a usable font is missing, since satori cannot lay out text without one.
135
+ */
136
+ async function renderWithSatori(
137
+ element: unknown,
138
+ options: ImageResponseOptions,
139
+ ): Promise<Uint8Array | undefined> {
140
+ const satori = await loadOptional<SatoriModule>('satori');
141
+ const resvg = await loadOptional<ResvgModule>('@resvg/resvg-js');
142
+ if (!satori || !resvg) return undefined;
143
+
144
+ const fonts = normalizeFonts(options.fonts);
145
+ if (fonts.length === 0) return undefined;
146
+
147
+ const width = dimension(options.width, DEFAULT_WIDTH, 'width');
148
+ const height = dimension(options.height, DEFAULT_HEIGHT, 'height');
149
+
150
+ const svg = await satori.default(element, {
151
+ width,
152
+ height,
153
+ fonts,
154
+ ...(options.debug === undefined ? {} : { debug: options.debug }),
155
+ ...(options.emoji === undefined ? {} : { emoji: options.emoji }),
156
+ });
157
+
158
+ const renderer = new resvg.Resvg(svg, {
159
+ fitTo: { mode: 'width', value: width },
160
+ });
161
+ return renderer.render().asPng();
162
+ }
163
+
164
+ interface SatoriFont {
165
+ name: string;
166
+ data: ArrayBuffer | Uint8Array;
167
+ weight?: number;
168
+ style?: 'normal' | 'italic';
169
+ }
170
+
171
+ function normalizeFonts(fonts: ImageResponseFont[] | undefined): SatoriFont[] {
172
+ if (!fonts) return [];
173
+ return fonts.map(font => ({
174
+ name: font.name,
175
+ data: font.data,
176
+ ...(font.weight === undefined ? {} : { weight: font.weight }),
177
+ ...(font.style === undefined ? {} : { style: font.style }),
178
+ }));
179
+ }
180
+
181
+ /**
182
+ * Resolve an optional dependency without letting a missing module become a hard
183
+ * build/runtime error. The dynamic specifier is opaque to the bundler so the
184
+ * optional dep is never eagerly required.
185
+ */
186
+ async function loadOptional<T>(name: string): Promise<T | undefined> {
187
+ try {
188
+ const specifier = name;
189
+ return (await import(/* @vite-ignore */ specifier)) as T;
190
+ } catch {
191
+ return undefined;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Resolve + import an optional dependency from the APP ROOT (process.cwd(), the
197
+ * pnext server's working dir at runtime), not from the bundled output's
198
+ * location. Mirrors the MDX plugin resolution: createRequire anchored at the
199
+ * project root turns the bare name into an absolute path, which the opaque
200
+ * dynamic import then loads. Returns undefined when the package is not
201
+ * installed in the user's app.
202
+ */
203
+ async function loadFromAppRoot<T>(name: string): Promise<T | undefined> {
204
+ try {
205
+ const resolveFrom = createRequire(path.join(process.cwd(), 'pnext-og-resolve.cjs'));
206
+ const resolved = resolveFrom.resolve(name);
207
+ return (await import(/* @vite-ignore */ resolved)) as T;
208
+ } catch {
209
+ return undefined;
210
+ }
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Fallback: a deterministic, valid 1-bit PNG of the requested dimensions. Not a
215
+ // rendering of the element, but a non-empty `image/png` body honoring the
216
+ // endpoint contract when satori/resvg (or a font) are unavailable.
217
+ // ---------------------------------------------------------------------------
218
+
219
+ function createFallbackPng(options: ImageResponseOptions) {
220
+ const width = dimension(options.width, DEFAULT_WIDTH, 'width');
221
+ const height = dimension(options.height, DEFAULT_HEIGHT, 'height');
222
+ const rowBytes = Math.ceil(width / 8) + 1;
223
+ const rawBytes = rowBytes * height;
224
+ if (rawBytes > MAX_RAW_IMAGE_BYTES) {
225
+ throw new RangeError(
226
+ `ImageResponse dimensions ${width}x${height} exceed the lightweight PNG limit.`,
227
+ );
228
+ }
229
+
230
+ const ihdr = new Uint8Array(13);
231
+ const ihdrView = new DataView(ihdr.buffer);
232
+ ihdrView.setUint32(0, width);
233
+ ihdrView.setUint32(4, height);
234
+ ihdr[8] = 1;
235
+
236
+ return concat(
237
+ PNG_SIGNATURE,
238
+ chunk('IHDR', ihdr),
239
+ chunk('IDAT', zlibStoreZeros(rawBytes)),
240
+ chunk('IEND', new Uint8Array()),
241
+ );
242
+ }
243
+
244
+ function dimension(value: number | undefined, fallback: number, name: string) {
245
+ if (value === undefined) return fallback;
246
+ if (!Number.isFinite(value) || value <= 0) {
247
+ throw new RangeError(`ImageResponse ${name} must be a positive finite number.`);
248
+ }
249
+ const rounded = Math.floor(value);
250
+ if (rounded > MAX_DIMENSION) {
251
+ throw new RangeError(`ImageResponse ${name} must be ${MAX_DIMENSION} pixels or less.`);
252
+ }
253
+ return rounded;
254
+ }
255
+
256
+ function zlibStoreZeros(length: number) {
257
+ const blockCount = Math.max(1, Math.ceil(length / 0xffff));
258
+ const output = new Uint8Array(2 + blockCount * 5 + length + 4);
259
+ output[0] = 0x78;
260
+ output[1] = 0x01;
261
+
262
+ let offset = 2;
263
+ let remaining = length;
264
+ while (remaining > 0 || offset === 2) {
265
+ const blockLength = Math.min(remaining, 0xffff);
266
+ remaining -= blockLength;
267
+ output[offset++] = remaining === 0 ? 0x01 : 0x00;
268
+ output[offset++] = blockLength & 0xff;
269
+ output[offset++] = (blockLength >> 8) & 0xff;
270
+ const inverse = 0xffff - blockLength;
271
+ output[offset++] = inverse & 0xff;
272
+ output[offset++] = (inverse >> 8) & 0xff;
273
+ offset += blockLength;
274
+ }
275
+
276
+ const adler = ((length % 65521) << 16) | 1;
277
+ new DataView(output.buffer).setUint32(output.length - 4, adler);
278
+ return output;
279
+ }
280
+
281
+ function chunk(type: string, data: Uint8Array) {
282
+ const output = new Uint8Array(12 + data.length);
283
+ const view = new DataView(output.buffer);
284
+ view.setUint32(0, data.length);
285
+ for (let index = 0; index < 4; index += 1) {
286
+ output[4 + index] = type.charCodeAt(index);
287
+ }
288
+ output.set(data, 8);
289
+ view.setUint32(output.length - 4, crc32(output.subarray(4, output.length - 4)));
290
+ return output;
291
+ }
292
+
293
+ function crc32(bytes: Uint8Array) {
294
+ const table = crcTable ?? createCrcTable();
295
+ crcTable = table;
296
+ let crc = 0xffffffff;
297
+ for (const byte of bytes) {
298
+ crc = table[(crc ^ byte) & 0xff]! ^ (crc >>> 8);
299
+ }
300
+ return (crc ^ 0xffffffff) >>> 0;
301
+ }
302
+
303
+ function createCrcTable() {
304
+ const table = new Uint32Array(256);
305
+ for (let index = 0; index < table.length; index += 1) {
306
+ let value = index;
307
+ for (let bit = 0; bit < 8; bit += 1) {
308
+ value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
309
+ }
310
+ table[index] = value >>> 0;
311
+ }
312
+ return table;
313
+ }
314
+
315
+ function concat(...parts: Uint8Array[]) {
316
+ const length = parts.reduce((total, part) => total + part.length, 0);
317
+ const output = new Uint8Array(length);
318
+ let offset = 0;
319
+ for (const part of parts) {
320
+ output.set(part, offset);
321
+ offset += part.length;
322
+ }
323
+ return output;
324
+ }
@@ -0,0 +1,188 @@
1
+ // Optimistic-routing route state (COMPAT - may import core freely).
2
+ //
3
+ // Next's client router builds a route-prediction trie from visited routes; a dynamic segment's trie node
4
+ // records its STATIC SIBLINGS (literal segments at the same URL level) so a prediction never shadows a
5
+ // static route. pnext ships the same information inline: the `__PNEXT_ROUTE__` state of a rendered
6
+ // document carries `staticChildren` - the literal sibling names at the route's deepest dynamic level.
7
+ //
8
+ // Siblings are computed from the ROUTE MANIFEST in URL space, which is what makes the Next-parity cases
9
+ // fall out for free: route groups collapse, parallel-route slot pages appear via their synthetic slot
10
+ // URLs, and page-less intermediate directories still surface as static children without leaking their
11
+ // nested names.
12
+
13
+ import type { RouteManifestEntry, RouteParamValue } from '../../types';
14
+ import { getRequestRuntime } from '../../routing/request-runtime';
15
+ import { getNextConfig } from './config-loader';
16
+
17
+ interface RouteState {
18
+ route: string;
19
+ params: Record<string, RouteParamValue>;
20
+ catchAllOptional?: boolean;
21
+ staticChildren?: string[];
22
+ staticChildrenBySegment?: Record<string, string[]>;
23
+ prefetchKind?: 'shell' | 'eager';
24
+ /**
25
+ * `prefetch = 'allow-runtime'`: the route's shell is RUNTIME-prefetched (request-sampled,
26
+ * param-derived), so unlike a static `partial` shell it is NOT shared across params - every URL of the
27
+ * route needs its own prefetch.
28
+ */
29
+ runtimePrefetch?: boolean;
30
+ /**
31
+ * The vary set of the BAKED SHELL these bytes came from (renderer's
32
+ * `bakedShellPageVary`). Passed straight through by the spread below: the
33
+ * client segment cache reads it off a hard-loaded document to key that
34
+ * document's static stage across the route's params.
35
+ */
36
+ pageVary?: string[];
37
+ }
38
+
39
+ /**
40
+ * `route.route` pattern segments in the client's bracket-free colon form (`:id`, `:slug*`). Both the
41
+ * manifest routes and the embedded `__PNEXT_ROUTE__` state use this encoding, so siblings computed here
42
+ * align with the pattern the client trie matches against - and no `[x]` placeholder ever reaches a body.
43
+ */
44
+ function publicSegments(route: string): string[] {
45
+ if (route === 'index' || route === '/') return [];
46
+ return route.split('/').filter(Boolean);
47
+ }
48
+
49
+ function isDynamicSegment(segment: string): boolean {
50
+ return segment.startsWith(':');
51
+ }
52
+
53
+ /**
54
+ * Literal sibling names at the deepest level of `pattern` (the level its dynamic/catch-all tail owns).
55
+ * A manifest route contributes the literal it places at that level when its leading segments are
56
+ * compatible with the pattern's prefix - literal-vs-literal must match, and a dynamic segment on either
57
+ * side matches anything. Over-collecting is safe: it only suppresses a prediction that then falls back
58
+ * to the server.
59
+ */
60
+ export function staticSiblingNames(
61
+ routes: readonly Pick<RouteManifestEntry, 'route' | 'kind' | 'interception'>[],
62
+ pattern: readonly string[],
63
+ ): string[] {
64
+ for (let index = pattern.length - 1; index >= 0; index--) {
65
+ if (isDynamicSegment(pattern[index]!)) return staticSiblingNamesAt(routes, pattern, index);
66
+ }
67
+ return [];
68
+ }
69
+
70
+ /** Literal static siblings for every dynamic level in a route prediction. */
71
+ export function staticSiblingNamesBySegment(
72
+ routes: readonly Pick<RouteManifestEntry, 'route' | 'kind' | 'interception'>[],
73
+ pattern: readonly string[],
74
+ ): Record<string, string[]> {
75
+ const result: Record<string, string[]> = {};
76
+ for (let index = 0; index < pattern.length; index++) {
77
+ if (!isDynamicSegment(pattern[index]!)) continue;
78
+ const siblings = staticSiblingNamesAt(routes, pattern, index);
79
+ // An empty array is meaningful: unlike an absent entry, it says this
80
+ // production route table knows there are no literal siblings here.
81
+ result[String(index)] = siblings;
82
+ }
83
+ return result;
84
+ }
85
+
86
+ function staticSiblingNamesAt(
87
+ routes: readonly Pick<RouteManifestEntry, 'route' | 'kind' | 'interception'>[],
88
+ pattern: readonly string[],
89
+ level: number,
90
+ ): string[] {
91
+ if (!isDynamicSegment(pattern[level]!)) return [];
92
+ const names = new Set<string>();
93
+ for (const route of routes) {
94
+ if (route.kind !== 'page' || route.interception) continue;
95
+ const segments = publicSegments(route.route);
96
+ if (segments.length <= level) continue;
97
+ let compatible = true;
98
+ for (let index = 0; index < level; index++) {
99
+ const own = pattern[index]!;
100
+ const other = segments[index]!;
101
+ if (isDynamicSegment(own) || isDynamicSegment(other)) continue;
102
+ if (own !== other) {
103
+ compatible = false;
104
+ break;
105
+ }
106
+ }
107
+ if (!compatible) continue;
108
+ const name = segments[level]!;
109
+ if (!isDynamicSegment(name)) names.add(name);
110
+ }
111
+ return [...names].sort();
112
+ }
113
+
114
+ /** True when the build enumerated exactly this param set for the route. */
115
+ function paramsPrerendered(
116
+ route: Pick<RouteManifestEntry, 'prerenderedParams' | 'params'>,
117
+ params: Record<string, RouteParamValue>,
118
+ ): boolean {
119
+ const sets = route.prerenderedParams;
120
+ if (!sets?.length) return false;
121
+ // A PPR SUB-shell carries only the params the build made concrete (the rest
122
+ // are still holes), so its state is not the full URL's — never "prerendered".
123
+ if (Object.keys(params).length !== (route.params ?? []).length) return false;
124
+ return sets.some(set => {
125
+ const names = Object.keys(set);
126
+ return (
127
+ names.length === Object.keys(params).length &&
128
+ names.every(name => String(set[name]) === String(params[name]))
129
+ );
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Enrich the client route state of a rendered document with the static
135
+ * siblings of every dynamic segment. Reads the route table from the
136
+ * request runtime (published by start/dev handlers and by the build's
137
+ * prerender pass); without one the state passes through unchanged and the
138
+ * client simply never predicts (safe fallback).
139
+ */
140
+ export function nextClientRouteState(
141
+ route: Pick<
142
+ RouteManifestEntry,
143
+ | 'route'
144
+ | 'file'
145
+ | 'hasStaticParams'
146
+ | 'usesRequest'
147
+ | 'segmentConfig'
148
+ | 'prerenderedParams'
149
+ | 'params'
150
+ | 'stream'
151
+ >,
152
+ state: RouteState,
153
+ ): RouteState {
154
+ const pattern = state.route.split('/').filter(Boolean);
155
+ const routes = getRequestRuntime()?.routes;
156
+ if (!routes) return state;
157
+ const staticChildrenBySegment = staticSiblingNamesBySegment(routes, pattern);
158
+ const staticChildren = staticSiblingNames(routes, pattern);
159
+ // Global `partialPrefetching: 'unstable_eager'` makes every route without a
160
+ // per-segment `prefetch` export eager (the App Shells Speculative-skip does
161
+ // not apply anywhere); a per-segment export still wins.
162
+ const globalEager =
163
+ (getNextConfig() as { partialPrefetching?: unknown }).partialPrefetching === 'unstable_eager';
164
+ const declared = route.segmentConfig?.prefetch;
165
+ // `generateStaticParams` only makes a route eager for the param sets it actually enumerated. When
166
+ // THIS URL's params were not among them and the route has a `loading` boundary, its page is dynamic
167
+ // here - the server answers the default prefetch with the loading shell, and calling it eager would
168
+ // escalate the reveal to a FULL prefetch that fetches the dynamic body and erases the loading state.
169
+ const partiallyEnumerated =
170
+ route.stream?.hasLoadingBoundary === true && !paramsPrerendered(route, state.params);
171
+ const prefetchKind =
172
+ declared === 'unstable_eager' ||
173
+ (declared === undefined &&
174
+ (globalEager || (route.hasStaticParams && !route.usesRequest && !partiallyEnumerated)))
175
+ ? 'eager'
176
+ : declared === 'allow-runtime' || declared === 'partial'
177
+ ? 'shell'
178
+ : undefined;
179
+ return {
180
+ ...state,
181
+ ...(staticChildren.length > 0 ? { staticChildren } : {}),
182
+ ...(Object.keys(staticChildrenBySegment).length > 0 ? { staticChildrenBySegment } : {}),
183
+ ...(prefetchKind ? { prefetchKind } : {}),
184
+ // Only `allow-runtime` shells are request-sampled per URL; a `partial` shell
185
+ // is a static prerender the whole route shares.
186
+ ...(declared === 'allow-runtime' ? { runtimePrefetch: true } : {}),
187
+ };
188
+ }
@@ -0,0 +1,39 @@
1
+ // `export const preferredRegion` extraction (COMPAT). Next records a route segment's preferredRegion in
2
+ // its build manifests as a normalized string[]. It is read straight off the route's source files via
3
+ // regex - the value is a static export, so a scan matches how `maxDuration` is already collected in core.
4
+
5
+ import { readFileSync } from 'node:fs';
6
+ import type { RouteManifestEntry } from '../../types';
7
+
8
+ const PREFERRED_REGION_RE =
9
+ /\bexport\s+const\s+preferredRegion\s*=\s*(\[[^\]]*\]|['"][^'"]*['"])/;
10
+
11
+ /**
12
+ * Resolve the route's `preferredRegion`, normalized to a string[] (Next accepts
13
+ * a single string or an array). Returns undefined when no segment declares it.
14
+ */
15
+ export function preferredRegionForRoute(route: RouteManifestEntry): string[] | undefined {
16
+ for (const file of routeSources(route)) {
17
+ let source: string;
18
+ try {
19
+ source = readFileSync(file, 'utf8');
20
+ } catch {
21
+ continue;
22
+ }
23
+ const match = PREFERRED_REGION_RE.exec(source);
24
+ if (!match?.[1]) continue;
25
+ return parsePreferredRegion(match[1]);
26
+ }
27
+ return undefined;
28
+ }
29
+
30
+ function routeSources(route: RouteManifestEntry): string[] {
31
+ return [...new Set([route.file, ...route.sourceFiles])].filter(Boolean);
32
+ }
33
+
34
+ function parsePreferredRegion(literal: string): string[] {
35
+ if (literal.startsWith('[')) {
36
+ return Array.from(literal.matchAll(/['"]([^'"]*)['"]/g), m => m[1] ?? '');
37
+ }
38
+ return [literal.slice(1, -1)];
39
+ }