@wular/pnext 0.0.4 → 0.0.6

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 (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1992
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1897
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +318 -296
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +827 -815
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
@@ -3,22 +3,22 @@
3
3
  // as the action wire's $$pnext_date) instead of throwing; the client/server
4
4
  // revive the marker back into the original view. assertSerializable accepts
5
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';
6
+ const TYPED_ARRAY_MARKER = '$$pnext_typed_array'
7
+ const ARRAY_BUFFER_MARKER = '$$pnext_array_buffer'
8
8
  // Map/Set are part of React's flight wire vocabulary (client components can receive them as props); pnext
9
9
  // does not stream them as distinct RSC row types yet, so they ride as ordinary marker objects, entries
10
10
  // encoded recursively like the binary markers above, instead of throwing "must be a plain object". Client
11
11
  // code that only round-trips the value sees a marker object rather than a live Map/Set instance - a known
12
12
  // gap, not a full flight-protocol Map/Set.
13
- const MAP_MARKER = '$$pnext_map';
14
- const SET_MARKER = '$$pnext_set';
13
+ const MAP_MARKER = '$$pnext_map'
14
+ const SET_MARKER = '$$pnext_set'
15
15
  // Cyclic props (a client component receiving `obj.self === obj`) are part of React's flight vocabulary:
16
16
  // flight emits a BACK-REFERENCE to the row/path the value was first written at rather than expanding it
17
17
  // forever. Same idea here, scoped to the ancestor chain: an object that reappears INSIDE ITSELF is written as
18
18
  // `{$$pnext_ref: [...path from the props root]}` and the client patches the original object back in,
19
19
  // restoring identity. Only true cycles are encoded this way - a value that merely appears twice in different
20
20
  // branches is still expanded, so every acyclic payload is byte-identical to what it was before.
21
- const REF_MARKER = '$$pnext_ref';
21
+ const REF_MARKER = '$$pnext_ref'
22
22
 
23
23
  type TypedArray =
24
24
  | Int8Array
@@ -31,26 +31,26 @@ type TypedArray =
31
31
  | Float32Array
32
32
  | Float64Array
33
33
  | BigInt64Array
34
- | BigUint64Array;
34
+ | BigUint64Array
35
35
 
36
36
  function isTypedArray(value: unknown): value is TypedArray {
37
- return ArrayBuffer.isView(value) && !(value instanceof DataView);
37
+ return ArrayBuffer.isView(value) && !(value instanceof DataView)
38
38
  }
39
39
 
40
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);
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
45
  }
46
46
 
47
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) } };
48
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
49
+ return { [TYPED_ARRAY_MARKER]: { type: value.constructor.name, data: bytesToBase64(bytes) } }
50
50
  }
51
51
 
52
52
  function encodeArrayBuffer(value: ArrayBuffer) {
53
- return { [ARRAY_BUFFER_MARKER]: bytesToBase64(new Uint8Array(value)) };
53
+ return { [ARRAY_BUFFER_MARKER]: bytesToBase64(new Uint8Array(value)) }
54
54
  }
55
55
 
56
56
  /**
@@ -66,24 +66,20 @@ function encodeBinary(
66
66
  ancestors = new Map<object, string[]>(),
67
67
  path: string[] = [],
68
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;
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
78
  }
79
79
 
80
- function encodeContainer(
81
- value: object,
82
- ancestors: Map<object, string[]>,
83
- path: string[],
84
- ): unknown {
80
+ function encodeContainer(value: object, ancestors: Map<object, string[]>, path: string[]): unknown {
85
81
  if (Array.isArray(value)) {
86
- return value.map((item, index) => encodeBinary(item, ancestors, [...path, String(index)]));
82
+ return value.map((item, index) => encodeBinary(item, ancestors, [...path, String(index)]))
87
83
  }
88
84
  if (value instanceof Map) {
89
85
  return {
@@ -91,22 +87,22 @@ function encodeContainer(
91
87
  encodeBinary(k, ancestors),
92
88
  encodeBinary(v, ancestors),
93
89
  ]),
94
- };
90
+ }
95
91
  }
96
92
  if (value instanceof Set) {
97
- return { [SET_MARKER]: [...value].map(item => encodeBinary(item, ancestors)) };
93
+ return { [SET_MARKER]: [...value].map(item => encodeBinary(item, ancestors)) }
98
94
  }
99
- if (Object.getPrototypeOf(value) !== Object.prototype) return value;
100
- const out: Record<string, unknown> = {};
95
+ if (Object.getPrototypeOf(value) !== Object.prototype) return value
96
+ const out: Record<string, unknown> = {}
101
97
  for (const [key, item] of Object.entries(value)) {
102
- out[key] = encodeBinary(item, ancestors, [...path, key]);
98
+ out[key] = encodeBinary(item, ancestors, [...path, key])
103
99
  }
104
- return out;
100
+ return out
105
101
  }
106
102
 
107
103
  // Errors reach `error.tsx` as props and ride the same marker convention (the
108
104
  // encoder for this one lives on the render side, with the boundary props).
109
- const ERROR_MARKER = '$$pnextError';
105
+ const ERROR_MARKER = '$$pnextError'
110
106
 
111
107
  /**
112
108
  * Revive the wire markers `serializeProps` wrote, IN PLACE:
@@ -124,22 +120,22 @@ export function reviveSerializedRefs<T>(
124
120
  root: T,
125
121
  extra?: (node: Record<string, unknown>) => unknown,
126
122
  ): T {
127
- if (root === null || typeof root !== 'object') return root;
123
+ if (root === null || typeof root !== 'object') return root
128
124
  const visit = (node: Record<string, unknown>): void => {
129
125
  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);
126
+ const item = node[key]
127
+ if (item === null || typeof item !== 'object' || ArrayBuffer.isView(item)) continue
128
+ const child = item as Record<string, unknown>
129
+ const revived = refTarget(root, child) ?? extra?.(child) ?? binaryTarget(child)
134
130
  if (revived !== undefined) {
135
- node[key] = revived;
136
- continue;
131
+ node[key] = revived
132
+ continue
137
133
  }
138
- visit(child);
134
+ visit(child)
139
135
  }
140
- };
141
- visit(root as Record<string, unknown>);
142
- return root;
136
+ }
137
+ visit(root as Record<string, unknown>)
138
+ return root
143
139
  }
144
140
 
145
141
  /**
@@ -149,17 +145,17 @@ export function reviveSerializedRefs<T>(
149
145
  * untouched when compat is off), and only compat entries import this name.
150
146
  */
151
147
  export function reviveSerializedErrorRefs<T>(root: T): T {
152
- return reviveSerializedRefs(root, errorTarget);
148
+ return reviveSerializedRefs(root, errorTarget)
153
149
  }
154
150
 
155
151
  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;
152
+ const marker = node[ERROR_MARKER]
153
+ if (marker === null || typeof marker !== 'object') return undefined
154
+ const data = marker as { name?: unknown; message?: unknown; digest?: unknown }
155
+ const error = new Error(typeof data.message === 'string' ? data.message : '')
156
+ if (typeof data.name === 'string') error.name = data.name
157
+ if (typeof data.digest === 'string') (error as Error & { digest?: string }).digest = data.digest
158
+ return error
163
159
  }
164
160
 
165
161
  // The wire carries the view's constructor NAME. Resolved off the global rather than a name-to-constructor
@@ -167,48 +163,48 @@ function errorTarget(node: Record<string, unknown>): Error | undefined {
167
163
  // so any other name - including one that resolves to something else entirely - resolves to undefined and the
168
164
  // marker is left alone.
169
165
  function typedArrayConstructor(name: string) {
170
- const constructor = (globalThis as unknown as Record<string, unknown>)[name];
166
+ const constructor = (globalThis as unknown as Record<string, unknown>)[name]
171
167
  return typeof constructor === 'function' && 'BYTES_PER_ELEMENT' in constructor
172
168
  ? (constructor as unknown as new (buffer: ArrayBuffer) => TypedArray)
173
- : undefined;
169
+ : undefined
174
170
  }
175
171
 
176
172
  // Decode side is client-only (the encoder is what runs on the server), so no
177
173
  // Buffer fast path — `atob` is global in every runtime that reaches this.
178
174
  function base64ToBytes(data: string): Uint8Array {
179
- return Uint8Array.from(atob(data), character => character.charCodeAt(0));
175
+ return Uint8Array.from(atob(data), character => character.charCodeAt(0))
180
176
  }
181
177
 
182
178
  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;
179
+ const buffer = node[ARRAY_BUFFER_MARKER]
180
+ if (typeof buffer === 'string') return base64ToBytes(buffer).buffer as ArrayBuffer
181
+ const typed = node[TYPED_ARRAY_MARKER]
182
+ if (typed === null || typeof typed !== 'object') return undefined
183
+ const { type, data } = typed as { type?: unknown; data?: unknown }
184
+ if (typeof type !== 'string' || typeof data !== 'string') return undefined
185
+ const Constructor = typedArrayConstructor(type)
186
+ return Constructor ? new Constructor(base64ToBytes(data).buffer as ArrayBuffer) : undefined
191
187
  }
192
188
 
193
189
  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;
190
+ const path = node[REF_MARKER]
191
+ if (!Array.isArray(path)) return undefined
192
+ let current: unknown = root
197
193
  for (const key of path) {
198
- if (current === null || typeof current !== 'object') return undefined;
199
- current = (current as Record<string, unknown>)[String(key)];
194
+ if (current === null || typeof current !== 'object') return undefined
195
+ current = (current as Record<string, unknown>)[String(key)]
200
196
  }
201
- return current;
197
+ return current
202
198
  }
203
199
 
204
200
  /** A preact vnode: `h` sets `constructor` to undefined on every one it makes. */
205
201
  export function isElementLike(value: unknown): boolean {
206
- if (value === null || typeof value !== 'object') return false;
202
+ if (value === null || typeof value !== 'object') return false
207
203
  return (
208
204
  (value as { constructor?: unknown }).constructor === undefined &&
209
205
  'type' in value &&
210
206
  'props' in value
211
- );
207
+ )
212
208
  }
213
209
 
214
210
  export function assertSerializable(
@@ -219,59 +215,59 @@ export function assertSerializable(
219
215
  // static-slots), so they are serializable there. Server-action args/returns keep throwing.
220
216
  allowElements = false,
221
217
  ) {
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;
218
+ if (value == null) return
219
+ if (allowElements && isElementLike(value)) return
220
+ const type = typeof value
221
+ if (type === 'string' || type === 'number' || type === 'boolean') return
226
222
  if (type === 'function' || type === 'symbol' || type === 'bigint' || type === 'undefined') {
227
- throw new Error(`${path} is not serializable: ${type}`);
223
+ throw new Error(`${path} is not serializable: ${type}`)
228
224
  }
229
225
  // Typed arrays / ArrayBuffers are serializable via the binary marker path.
230
- if (isTypedArray(value) || value instanceof ArrayBuffer) return;
226
+ if (isTypedArray(value) || value instanceof ArrayBuffer) return
231
227
  if (value instanceof Date || value instanceof URL) {
232
- throw new Error(`${path} must be a plain JSON value`);
228
+ throw new Error(`${path} must be a plain JSON value`)
233
229
  }
234
- if (typeof value !== 'object') return;
230
+ if (typeof value !== 'object') return
235
231
  // A value that contains ITSELF is serializable: encodeBinary writes it as a
236
232
  // `$$pnext_ref` back-reference (React flight does the same), so stop walking
237
233
  // rather than rejecting the props.
238
- if (seen.has(value)) return;
239
- seen.add(value);
234
+ if (seen.has(value)) return
235
+ seen.add(value)
240
236
 
241
237
  if (Array.isArray(value)) {
242
238
  value.forEach((item, index) =>
243
239
  assertSerializable(item, `${path}[${index}]`, seen, allowElements),
244
- );
245
- return;
240
+ )
241
+ return
246
242
  }
247
243
 
248
- // The slot walk (islands/static-slots) never enters Map/Set, so elements inside them must
244
+ // The slot walk (render/static-slots) never enters Map/Set, so elements inside them must
249
245
  // still throw rather than ship mangled.
250
246
  if (value instanceof Map) {
251
- let index = 0;
247
+ let index = 0
252
248
  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++;
249
+ assertSerializable(key, `${path}<Map key ${index}>`, seen)
250
+ assertSerializable(item, `${path}<Map value ${index}>`, seen)
251
+ index++
256
252
  }
257
- return;
253
+ return
258
254
  }
259
255
 
260
256
  if (value instanceof Set) {
261
- let index = 0;
257
+ let index = 0
262
258
  for (const item of value) {
263
- assertSerializable(item, `${path}<Set item ${index}>`, seen);
264
- index++;
259
+ assertSerializable(item, `${path}<Set item ${index}>`, seen)
260
+ index++
265
261
  }
266
- return;
262
+ return
267
263
  }
268
264
 
269
265
  if (Object.getPrototypeOf(value) !== Object.prototype) {
270
- throw new Error(`${path} must be a plain object`);
266
+ throw new Error(`${path} must be a plain object`)
271
267
  }
272
268
 
273
269
  for (const [key, item] of Object.entries(value)) {
274
- assertSerializable(item, `${path}.${key}`, seen, allowElements);
270
+ assertSerializable(item, `${path}.${key}`, seen, allowElements)
275
271
  }
276
272
  }
277
273
 
@@ -291,26 +287,26 @@ export function assertSerializable(
291
287
  // original) must not split the tainted set.
292
288
  // ---------------------------------------------------------------------------
293
289
 
294
- const TAINT_REGISTRY_KEY = '__pnext_tainted_values__';
290
+ const TAINT_REGISTRY_KEY = '__pnext_tainted_values__'
295
291
 
296
292
  /** Dev message plus the redacted text prod shows in its place. */
297
293
  export interface TaintMessages {
298
- dev: string;
299
- prod: string;
294
+ dev: string
295
+ prod: string
300
296
  }
301
297
 
302
298
  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;
299
+ const host = globalThis as { [TAINT_REGISTRY_KEY]?: Map<object, TaintMessages> }
300
+ const existing = host[TAINT_REGISTRY_KEY]
301
+ if (existing) return existing
302
+ const created = new Map<object, TaintMessages>()
303
+ host[TAINT_REGISTRY_KEY] = created
304
+ return created
309
305
  }
310
306
 
311
307
  /** Register `value` as unsafe to pass to a client component. */
312
308
  export function taintObjectReference(messages: TaintMessages, value: object): void {
313
- taintRegistry().set(value, messages);
309
+ taintRegistry().set(value, messages)
314
310
  }
315
311
 
316
312
  /**
@@ -319,60 +315,57 @@ export function taintObjectReference(messages: TaintMessages, value: object): vo
319
315
  * identity the check relies on).
320
316
  */
321
317
  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);
318
+ const registry = taintRegistry()
319
+ if (registry.size === 0) return
320
+ if (value === null || typeof value !== 'object') return
321
+ const messages = registry.get(value)
326
322
  if (messages !== undefined) {
327
323
  // Next surfaces the explanatory message in dev only; prod renders React's
328
324
  // redacted RSC error text in the nearest error boundary.
329
325
  throw new Error(
330
326
  // eslint-disable-next-line turbo/no-undeclared-env-vars
331
327
  process.env.NODE_ENV === 'development' ? messages.dev : messages.prod,
332
- );
328
+ )
333
329
  }
334
- if (seen.has(value)) return;
335
- seen.add(value);
330
+ if (seen.has(value)) return
331
+ seen.add(value)
336
332
  if (Array.isArray(value)) {
337
- for (const item of value) assertNotTainted(item, seen);
338
- return;
333
+ for (const item of value) assertNotTainted(item, seen)
334
+ return
339
335
  }
340
336
  if (value instanceof Map) {
341
337
  for (const [key, item] of value.entries()) {
342
- assertNotTainted(key, seen);
343
- assertNotTainted(item, seen);
338
+ assertNotTainted(key, seen)
339
+ assertNotTainted(item, seen)
344
340
  }
345
- return;
341
+ return
346
342
  }
347
343
  if (value instanceof Set) {
348
- for (const item of value) assertNotTainted(item, seen);
349
- return;
344
+ for (const item of value) assertNotTainted(item, seen)
345
+ return
350
346
  }
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);
347
+ const proto = Object.getPrototypeOf(value) as object | null
348
+ if (proto !== Object.prototype && proto !== null) return
349
+ for (const item of Object.values(value)) assertNotTainted(item, seen)
354
350
  }
355
351
 
356
352
  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
- );
353
+ assertNotTainted(value)
354
+ assertSerializable(value, 'props', new Set(), options?.allowElements)
355
+ return JSON.stringify(encodeBinary(value)).replace(/[<>&\u2028\u2029]/g, char => {
356
+ switch (char) {
357
+ case '<':
358
+ return '\\u003c'
359
+ case '>':
360
+ return '\\u003e'
361
+ case '&':
362
+ return '\\u0026'
363
+ case '\u2028':
364
+ return '\\u2028'
365
+ case '\u2029':
366
+ return '\\u2029'
367
+ default:
368
+ return char
369
+ }
370
+ })
378
371
  }
@@ -1,39 +1,39 @@
1
- import { dim } from './ansi';
1
+ import { dim } from './ansi'
2
2
 
3
3
  export interface VerboseLogger {
4
- readonly enabled: boolean;
5
- log(message: string): void;
6
- step<T>(label: string, task: () => Promise<T>): Promise<T>;
4
+ readonly enabled: boolean
5
+ log(message: string): void
6
+ step<T>(label: string, task: () => Promise<T>): Promise<T>
7
7
  }
8
8
 
9
9
  export function createVerboseLogger(enabled: boolean, scope: string): VerboseLogger {
10
- const prefix = dim(`pnext ${scope}`);
10
+ const prefix = dim(`pnext ${scope}`)
11
11
  // Stamp every line with time-since-process-start: once stages run in
12
12
  // parallel their lines interleave, and only the stamps say what overlapped.
13
13
  const write = (message: string) =>
14
- console.log(`${prefix} ${dim(`@${(performance.now() / 1000).toFixed(2)}s`)} ${message}`);
14
+ console.log(`${prefix} ${dim(`@${(performance.now() / 1000).toFixed(2)}s`)} ${message}`)
15
15
 
16
16
  return {
17
17
  enabled,
18
18
  log(message) {
19
- if (enabled) write(message);
19
+ if (enabled) write(message)
20
20
  },
21
21
  async step(label, task) {
22
- if (!enabled) return task();
23
- write(label);
24
- const start = performance.now();
22
+ if (!enabled) return task()
23
+ write(label)
24
+ const start = performance.now()
25
25
  try {
26
- return await task();
26
+ return await task()
27
27
  } finally {
28
- write(` ${dim(`↳ ${label} ${formatDuration(performance.now() - start)}`)}`);
28
+ write(` ${dim(`↳ ${label} ${formatDuration(performance.now() - start)}`)}`)
29
29
  }
30
30
  },
31
- };
31
+ }
32
32
  }
33
33
 
34
- function formatDuration(durationMs: number) {
35
- const ms = Math.max(0, durationMs);
36
- if (ms < 10) return `${ms.toFixed(1)}ms`;
37
- if (ms < 1000) return `${Math.round(ms)}ms`;
38
- return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`;
34
+ export function formatDuration(durationMs: number) {
35
+ const ms = Math.max(0, durationMs)
36
+ if (ms < 10) return `${ms.toFixed(1)}ms`
37
+ if (ms < 1000) return `${Math.round(ms)}ms`
38
+ return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`
39
39
  }