@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -3,11 +3,16 @@
3
3
  /**
4
4
  * Component to render collected meta descriptors in the document head.
5
5
  *
6
- * Supports both sync and async meta descriptors. Async descriptors
7
- * (Promise<MetaDescriptorBase>) are resolved using React's use() hook.
6
+ * Deferred (Promise) meta descriptors are resolved before MetaTags renders
7
+ * (server-side on the full render, client-side before apply on navigation), so
8
+ * it only ever receives resolved descriptors and never suspends.
8
9
  *
9
10
  * When theme is enabled in the router config, MetaTags also renders
10
11
  * the theme initialization script to prevent FOUC (flash of unstyled content).
12
+ * This makes MetaTags the sole FOUC-script injector for apps that render it;
13
+ * the standalone `<ThemeScript />` is only needed when MetaTags is not used.
14
+ * Rendering both is safe (the inline script guards listener registration) but
15
+ * redundant.
11
16
  *
12
17
  * @example
13
18
  * ```tsx
@@ -24,13 +29,13 @@
24
29
  * ```
25
30
  */
26
31
 
27
- import { use } from "react";
28
32
  import { useHandle } from "../browser/react/use-handle.js";
29
33
  import { Meta } from "./meta.js";
30
- import type { MetaDescriptor, MetaDescriptorBase } from "../router/types.js";
34
+ import type { MetaDescriptorBase } from "../router/types.js";
31
35
  import { useThemeContext } from "../theme/theme-context.js";
32
36
  import { generateThemeScript } from "../theme/theme-script.js";
33
37
  import { useNonce } from "../browser/react/nonce-context.js";
38
+ import { escapeJsonForScript } from "../escape-script.js";
34
39
 
35
40
  // Type guards for MetaDescriptorBase variants
36
41
  function hasCharSet(d: MetaDescriptorBase): d is { charSet: "utf-8" } {
@@ -90,31 +95,18 @@ function hasTagName(
90
95
  );
91
96
  }
92
97
 
93
- /**
94
- * Check if a value is a Promise.
95
- */
96
- function isPromise(value: unknown): value is Promise<unknown> {
97
- return value !== null && typeof value === "object" && "then" in value;
98
- }
99
-
100
- /**
101
- * Render a single meta descriptor as a React element.
102
- */
103
- function renderMetaDescriptor(
98
+ export function renderMetaDescriptor(
104
99
  descriptor: MetaDescriptorBase,
105
100
  index: number,
106
101
  ): React.ReactNode {
107
- // charset
108
102
  if (hasCharSet(descriptor)) {
109
103
  return <meta key="charSet" charSet={descriptor.charSet} />;
110
104
  }
111
105
 
112
- // title
113
106
  if (hasTitle(descriptor)) {
114
107
  return <title key="title">{descriptor.title}</title>;
115
108
  }
116
109
 
117
- // name + content (description, viewport, etc.)
118
110
  if (hasNameContent(descriptor)) {
119
111
  return (
120
112
  <meta
@@ -125,7 +117,6 @@ function renderMetaDescriptor(
125
117
  );
126
118
  }
127
119
 
128
- // property + content (Open Graph, etc.)
129
120
  if (hasPropertyContent(descriptor)) {
130
121
  return (
131
122
  <meta
@@ -136,7 +127,6 @@ function renderMetaDescriptor(
136
127
  );
137
128
  }
138
129
 
139
- // http-equiv + content
140
130
  if (hasHttpEquivContent(descriptor)) {
141
131
  return (
142
132
  <meta
@@ -147,9 +137,10 @@ function renderMetaDescriptor(
147
137
  );
148
138
  }
149
139
 
150
- // JSON-LD structured data
151
140
  if (hasScriptLdJson(descriptor)) {
152
- const json = JSON.stringify(descriptor["script:ld+json"]);
141
+ const json = escapeJsonForScript(
142
+ JSON.stringify(descriptor["script:ld+json"]),
143
+ );
153
144
  return (
154
145
  <script
155
146
  key={`ld-json-${index}`}
@@ -159,7 +150,6 @@ function renderMetaDescriptor(
159
150
  );
160
151
  }
161
152
 
162
- // Custom tagName (meta or link with arbitrary attributes)
163
153
  if (hasTagName(descriptor)) {
164
154
  const { tagName, ...rest } = descriptor;
165
155
  if (tagName === "link") {
@@ -180,7 +170,6 @@ function renderMetaDescriptor(
180
170
  }
181
171
  }
182
172
 
183
- // Fallback: treat as meta attributes
184
173
  return (
185
174
  <meta
186
175
  key={`meta-fallback-${index}`}
@@ -189,20 +178,6 @@ function renderMetaDescriptor(
189
178
  );
190
179
  }
191
180
 
192
- /**
193
- * Wrapper component to resolve a Promise<MetaDescriptorBase> using use().
194
- */
195
- function AsyncMetaTag({
196
- promise,
197
- index,
198
- }: {
199
- promise: Promise<MetaDescriptorBase>;
200
- index: number;
201
- }): React.ReactNode {
202
- const resolved = use(promise);
203
- return renderMetaDescriptor(resolved, index);
204
- }
205
-
206
181
  /**
207
182
  * Renders all collected meta descriptors from route handlers.
208
183
  *
@@ -212,11 +187,16 @@ function AsyncMetaTag({
212
187
  * When theme is enabled in router config, also renders the theme initialization
213
188
  * script to prevent FOUC (flash of unstyled content).
214
189
  *
215
- * Async meta descriptors (Promise<MetaDescriptorBase>) are resolved using
216
- * React's use() hook. RSC streaming handles the Promise resolution.
190
+ * Deferred (Promise) meta descriptors are resolved BEFORE MetaTags renders —
191
+ * server-side on the full/SSR render, client-side before apply on navigation
192
+ * (resolve-by-default) — so MetaTags only ever receives resolved descriptors and
193
+ * never suspends.
217
194
  */
218
195
  export function MetaTags(): React.ReactNode {
219
- const descriptors = useHandle(Meta) as MetaDescriptor[];
196
+ // Deferred descriptors are resolved BEFORE collect runs (resolve-by-default),
197
+ // and collectMeta strips unset markers, so the collected output is always
198
+ // resolved base descriptors (never a Promise).
199
+ const descriptors = useHandle(Meta) as MetaDescriptorBase[];
220
200
  const themeConfig = useThemeContext()?.config ?? null;
221
201
  const nonce = useNonce();
222
202
 
@@ -229,18 +209,9 @@ export function MetaTags(): React.ReactNode {
229
209
  dangerouslySetInnerHTML={{ __html: generateThemeScript(themeConfig) }}
230
210
  />
231
211
  )}
232
- {descriptors.map((descriptor, index) => {
233
- if (isPromise(descriptor)) {
234
- return (
235
- <AsyncMetaTag
236
- key={`async-${index}`}
237
- promise={descriptor}
238
- index={index}
239
- />
240
- );
241
- }
242
- return renderMetaDescriptor(descriptor, index);
243
- })}
212
+ {descriptors.map((descriptor, index) =>
213
+ renderMetaDescriptor(descriptor, index),
214
+ )}
244
215
  </>
245
216
  );
246
217
  }
@@ -0,0 +1,183 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Renders the scripts collected by the Script handle into the document.
5
+ *
6
+ * Place `<Scripts />` inside `<head>` (default) and, if you push body scripts,
7
+ * `<Scripts position="body" />` at the top of `<body>`. Each site renders the
8
+ * configs whose `position` matches; the request CSP nonce is applied
9
+ * automatically to every DOCUMENT-RENDERED <script> (consumers never pass it). An
10
+ * async script first encountered on a soft navigation is injected client-side
11
+ * where the nonce is unavailable, so it carries no nonce and relies on
12
+ * 'strict-dynamic' (or a host allowance) — see the nonce caveat in the /scripts
13
+ * skill.
14
+ *
15
+ * EXECUTION CONTRACT — see the Script handle's docs. Inline + ordered (defer)
16
+ * scripts are document-load: they execute only when present in the initial HTML,
17
+ * so this component FREEZES that set after hydration (the initializer below runs
18
+ * once) — a later soft navigation never inserts an inert <script> (React creates
19
+ * client-mounted scripts via innerHTML, which the HTML spec makes non-executing).
20
+ * Async external scripts are React resources and stay reactive: React loads them
21
+ * on first encounter, including after navigation, deduped by src.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <html>
26
+ * <head>
27
+ * <MetaTags />
28
+ * <Scripts />
29
+ * </head>
30
+ * <body>
31
+ * <Scripts position="body" />
32
+ * {children}
33
+ * </body>
34
+ * </html>
35
+ * ```
36
+ */
37
+
38
+ import { useState, type ReactNode } from "react";
39
+ import { useHandle } from "../browser/react/use-handle.js";
40
+ import { useNonce } from "../browser/react/nonce-context.js";
41
+ import { escapeScriptBody } from "../escape-script.js";
42
+ import { Script, type ScriptAttributes, type ScriptConfig } from "./script.js";
43
+
44
+ /** An external async script is a React-managed resource (reactive on nav). */
45
+ function isAsyncResource(config: ScriptConfig): boolean {
46
+ return config.src != null && config.async === true;
47
+ }
48
+
49
+ // Fields the Script handle owns (set via the ScriptConfig fields, applied as
50
+ // explicit props by renderScript) plus the inline-content props. Dropped from the
51
+ // attributes bag so untyped/serialized input cannot smuggle them in — e.g.
52
+ // `children`/`dangerouslySetInnerHTML` alongside an inline body makes React throw,
53
+ // or `src` on an inline script. The discriminated type already excludes these;
54
+ // this is the runtime guard.
55
+ const MANAGED_ATTRS = new Set([
56
+ "id",
57
+ "src",
58
+ "async",
59
+ "defer",
60
+ "type",
61
+ "children",
62
+ "nonce",
63
+ "dangerouslySetInnerHTML",
64
+ ]);
65
+
66
+ // Drop managed fields + any `on*` event handlers (a config serializes across the
67
+ // server -> client boundary, so a function cannot survive it) from the passthrough
68
+ // attributes, warning in dev.
69
+ function passthroughAttributes(
70
+ attributes: ScriptAttributes | undefined,
71
+ ): Record<string, unknown> {
72
+ if (!attributes) return {};
73
+ const out: Record<string, unknown> = {};
74
+ const dev = process.env.NODE_ENV !== "production";
75
+ for (const [key, value] of Object.entries(
76
+ attributes as Record<string, unknown>,
77
+ )) {
78
+ const isHandler = key.startsWith("on");
79
+ if (isHandler || MANAGED_ATTRS.has(key)) {
80
+ if (dev) {
81
+ console.warn(
82
+ isHandler
83
+ ? `[Scripts] event handler "${key}" in a script's attributes is ` +
84
+ `dropped; callbacks cannot cross the server -> client handle ` +
85
+ `boundary. Use a "use client" component for load/error handling.`
86
+ : `[Scripts] managed field "${key}" in a script's attributes is ` +
87
+ `dropped; set it via the ScriptConfig fields (the request nonce ` +
88
+ `is applied automatically).`,
89
+ );
90
+ }
91
+ continue;
92
+ }
93
+ out[key] = value;
94
+ }
95
+ return out;
96
+ }
97
+
98
+ function renderScript(
99
+ config: ScriptConfig,
100
+ nonce: string | undefined,
101
+ index: number,
102
+ ): ReactNode {
103
+ const { id, src, children, async, defer, type, attributes } = config;
104
+ const key = id ?? src ?? `rango-script-${index}`;
105
+ const attrs = passthroughAttributes(attributes);
106
+
107
+ // Inline: rendered in place (never hoisted), escaped against </script> breakout.
108
+ // The server-only nonce makes the attribute differ from the (undefined) client
109
+ // value, so suppressHydrationWarning is required — the same sanctioned pattern
110
+ // as the theme/Meta inline scripts.
111
+ if (src == null) {
112
+ if (children == null) return null;
113
+ return (
114
+ <script
115
+ key={key}
116
+ {...attrs}
117
+ id={id}
118
+ type={type}
119
+ nonce={nonce}
120
+ suppressHydrationWarning
121
+ dangerouslySetInnerHTML={{ __html: escapeScriptBody(children) }}
122
+ />
123
+ );
124
+ }
125
+
126
+ if (
127
+ process.env.NODE_ENV !== "production" &&
128
+ config.position === "body" &&
129
+ async
130
+ ) {
131
+ console.warn(
132
+ `[Scripts] An async external script (src="${src}") is hoisted into ` +
133
+ `<head> by React; position: "body" is ignored for it.`,
134
+ );
135
+ }
136
+
137
+ // External: async => React-hoisted, src-deduped resource; otherwise in place
138
+ // (defer or plain), preserving authoring order.
139
+ return (
140
+ <script
141
+ key={key}
142
+ {...attrs}
143
+ id={id}
144
+ type={type}
145
+ src={src}
146
+ async={async}
147
+ defer={defer}
148
+ nonce={nonce}
149
+ suppressHydrationWarning
150
+ />
151
+ );
152
+ }
153
+
154
+ export function Scripts({
155
+ position = "head",
156
+ }: { position?: "head" | "body" } = {}): ReactNode {
157
+ const all = useHandle(Script) as ScriptConfig[];
158
+ const nonce = useNonce();
159
+
160
+ const forPosition = all.filter(
161
+ (config) => (config.position ?? "head") === position,
162
+ );
163
+
164
+ // Document-load scripts (inline + ordered external) execute only from the
165
+ // initial HTML, so freeze them to the first-render set. The initializer runs
166
+ // during SSR and again at hydration with the same handle data, so the output
167
+ // matches; afterwards a navigation cannot add an inert <script>.
168
+ const [documentLoad] = useState(() =>
169
+ forPosition.filter((config) => !isAsyncResource(config)),
170
+ );
171
+ // Async external scripts are resources React loads on first encounter; keep
172
+ // them reactive so a script first reached via navigation still loads.
173
+ const asyncResources = forPosition.filter(isAsyncResource);
174
+
175
+ return (
176
+ <>
177
+ {documentLoad.map((config, index) => renderScript(config, nonce, index))}
178
+ {asyncResources.map((config, index) =>
179
+ renderScript(config, nonce, index),
180
+ )}
181
+ </>
182
+ );
183
+ }
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Each layout/route pushes breadcrumb items via `ctx.use(Breadcrumbs)`.
5
5
  * Items are collected in parent-to-child order with automatic deduplication
6
- * by `href` (last item for each href wins).
6
+ * by `href`: each href keeps its FIRST position but takes the LAST value, so a
7
+ * child re-pushing a parent href refreshes the label without reordering the trail.
7
8
  *
8
9
  * @example
9
10
  * ```tsx
@@ -38,19 +39,45 @@ export interface BreadcrumbItem {
38
39
 
39
40
  /**
40
41
  * Collect function for Breadcrumbs handle.
41
- * Flattens segments in parent-to-child order with deduplication by href
42
- * (last item for each href wins).
42
+ * Flattens segments in parent-to-child order with deduplication by href: each
43
+ * href keeps its FIRST position but takes the LAST value (re-pushing a parent
44
+ * href refreshes the label in place without reordering the trail). Deferred
45
+ * crumbs (a pushed Promise or `ctx.use(Breadcrumbs).defer()`) are resolved
46
+ * BEFORE collect runs (resolve-by-default), so collect only ever sees resolved
47
+ * items. An item without a string `href` is passed through by identity and
48
+ * excluded from the href dedup.
43
49
  */
44
50
  function collectBreadcrumbs(segments: BreadcrumbItem[][]): BreadcrumbItem[] {
45
51
  const all = segments.flat();
46
- const seen = new Map<string, number>();
47
52
 
48
- for (let i = 0; i < all.length; i++) {
49
- seen.set(all[i].href, i);
53
+ const hasHref = (item: unknown): item is BreadcrumbItem =>
54
+ item != null &&
55
+ typeof item === "object" &&
56
+ typeof (item as { href?: unknown }).href === "string";
57
+
58
+ // Dedup crumbs by href: keep the FIRST position (preserving parent->child
59
+ // order) but the LAST value (a child re-pushing a parent's href can refresh
60
+ // its label). Items without an href pass through by identity at their original
61
+ // position.
62
+ const valueByHref = new Map<string, BreadcrumbItem>();
63
+ for (const item of all) {
64
+ if (hasHref(item)) valueByHref.set(item.href, item);
50
65
  }
51
66
 
52
- // Return items in order, keeping only the last occurrence per href
53
- return all.filter((item, index) => seen.get(item.href) === index);
67
+ const result: BreadcrumbItem[] = [];
68
+ const emitted = new Set<string>();
69
+ for (const item of all) {
70
+ if (!hasHref(item)) {
71
+ result.push(item);
72
+ continue;
73
+ }
74
+ // Emit each href once, at its first occurrence, with the final value.
75
+ if (!emitted.has(item.href)) {
76
+ emitted.add(item.href);
77
+ result.push(valueByHref.get(item.href)!);
78
+ }
79
+ }
80
+ return result;
54
81
  }
55
82
 
56
83
  /**
@@ -0,0 +1,127 @@
1
+ import type { HandleData, HandleStore } from "../server/handle-store.js";
2
+ import { isThenable } from "./is-thenable.js";
3
+
4
+ /**
5
+ * Runtime-neutral resolution of deferred (Promise) handle values. Shared by the
6
+ * server full-render path (resolve before the payload is finalized so SSR sees
7
+ * resolved values) and the client soft-nav path (NavigationProvider resolves
8
+ * each yield before applying it). Pure — imports only `isThenable` — so it is
9
+ * safe in both the RSC server environment AND the browser.
10
+ *
11
+ * Resolution is SHALLOW: only an entry that is ITSELF a thenable is awaited. A
12
+ * value that merely contains a promise (e.g. `{ data: somePromise }`) is a
13
+ * non-thenable, so the `isThenable` check skips it and it passes through verbatim
14
+ * — the nested promise is the consumer's responsibility. Resolution runs each
15
+ * entry through `Promise.all` with a per-entry `try/catch` (NOT `allSettled`), so
16
+ * one rejecting entry is dropped without rejecting the batch or aborting siblings.
17
+ */
18
+
19
+ /**
20
+ * Handle names ($$id) whose snapshot carries a deferred (top-level Promise)
21
+ * value. Used by the client to decide which buckets to hold-and-resolve, and
22
+ * `.size > 0` answers "does this snapshot have any deferred value".
23
+ */
24
+ export function deferredHandleNames(data: HandleData): Set<string> {
25
+ const names = new Set<string>();
26
+ for (const [handleName, segments] of Object.entries(data)) {
27
+ for (const values of Object.values(segments)) {
28
+ if (values.some(isThenable)) {
29
+ names.add(handleName);
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ return names;
35
+ }
36
+
37
+ /**
38
+ * Snapshot with deferred (Promise) values awaited (shallow — only top-level
39
+ * thenables). A rejected deferred is dropped, and a deferred that resolves to
40
+ * `null`/`undefined` is dropped (the `.defer({ else })` skip semantics), so a
41
+ * nullish deferred slot never reaches a collector. Sync (non-thenable) values —
42
+ * including a legitimate sync `null` push — pass through unchanged.
43
+ */
44
+ export async function resolveDeferredHandleValues(
45
+ data: HandleData,
46
+ ): Promise<HandleData> {
47
+ const out: HandleData = {};
48
+ await Promise.all(
49
+ Object.entries(data).flatMap(([handleName, segments]) => {
50
+ out[handleName] = {};
51
+ return Object.entries(segments).map(async ([segmentId, values]) => {
52
+ out[handleName][segmentId] = await resolveValues(values);
53
+ });
54
+ }),
55
+ );
56
+ return out;
57
+ }
58
+
59
+ // Resolve each entry IN ORDER. A non-thenable passes through verbatim (including
60
+ // a legitimate sync `null`/`undefined` push — the "await iff thenable" contract).
61
+ // A thenable is awaited and dropped when it rejects OR resolves to null/undefined
62
+ // (the `.defer({ else })` skip semantics: a forgotten resolver times out to
63
+ // `else ?? undefined`), so a nullish deferred slot never reaches a collector.
64
+ async function resolveValues(values: unknown[]): Promise<unknown[]> {
65
+ const resolved = await Promise.all(
66
+ values.map(async (v) => {
67
+ if (!isThenable(v)) return { keep: true, value: v };
68
+ try {
69
+ const value = await v;
70
+ return { keep: value != null, value };
71
+ } catch {
72
+ return { keep: false, value: undefined };
73
+ }
74
+ }),
75
+ );
76
+ return resolved.filter((r) => r.keep).map((r) => r.value);
77
+ }
78
+
79
+ /**
80
+ * Full-render handle stream: a one-shot async generator that yields the FINAL
81
+ * handle snapshot with every top-level deferred value resolved. Drop-in for
82
+ * `handleStore.stream()` on full / HTML renders (initial load, 404, progressive
83
+ * enhancement), where the consumer drains the generator to completion before
84
+ * rendering anyway — so the single final yield is equivalent to the streamed
85
+ * yields' converged state, and the SSR markup plus the first sync `useHandle`
86
+ * read see resolved values. `getData()` seals the store and awaits the handler
87
+ * barrier; `resolveDeferredHandleValues` then awaits the pushed-value promises.
88
+ * A hung handle promise blocks the full render like any unresolved await (no
89
+ * handle-specific timeout). Partial / action payloads keep streaming via
90
+ * `handleStore.stream()`.
91
+ */
92
+ export async function* resolvedHandleStream(
93
+ handleStore: HandleStore,
94
+ ): AsyncGenerator<HandleData, void, unknown> {
95
+ // Drain stream() (NOT getData()) for the converged snapshot: stream() sets the
96
+ // store's `completed` flag on seal+settle, and that flag is what makes a LATE
97
+ // push throw LateHandlePushError — an async JSX subtree that suspended and later
98
+ // calls ctx.use(Handle)(...) after collection. getData() never sets `completed`,
99
+ // so the late push would silently land. Both wait for the same settle barrier, so
100
+ // the final yielded value is identical; we just keep the late-push guard.
101
+ let snapshot: HandleData = {};
102
+ for await (const data of handleStore.stream()) {
103
+ snapshot = data;
104
+ }
105
+ yield await resolveDeferredHandleValues(snapshot);
106
+ }
107
+
108
+ /**
109
+ * Resolve the deferred (Promise) values in ONE segment's handle bucket
110
+ * (`handleName -> entries[]`). Same shallow + drop-rejections rule as
111
+ * {@link resolveDeferredHandleValues}, but for the segment-keyed shape the
112
+ * prerender/static collection paths use (`getDataForSegment`). Prerender is a
113
+ * build-time cache, so the baked artifact must hold resolved values — a hit
114
+ * replays them into the segment system, where collect/useHandle expect resolved
115
+ * data.
116
+ */
117
+ export async function resolveSegmentHandleValues(
118
+ segHandles: Record<string, unknown[]>,
119
+ ): Promise<Record<string, unknown[]>> {
120
+ const out: Record<string, unknown[]> = {};
121
+ await Promise.all(
122
+ Object.entries(segHandles).map(async ([handleName, values]) => {
123
+ out[handleName] = await resolveValues(values);
124
+ }),
125
+ );
126
+ return out;
127
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Single thenable predicate for the resolve-by-default handle machinery
3
+ * (`handles/deferred-resolution.ts`, its sole owner) — it decides which top-level
4
+ * handle entries are deferred (`Promise`) values to await before any consumer
5
+ * sees them, and which pass through verbatim.
6
+ *
7
+ * Requires a CALLABLE `then` (`typeof obj.then === "function"`), not merely a
8
+ * `"then" in obj` membership check, so a non-promise carrying a `then` field
9
+ * (e.g. a serialized shape `{ then: 5 }`) is treated as a plain sync value rather
10
+ * than awaited. A single predicate keeps that classification consistent.
11
+ */
12
+ export function isThenable(value: unknown): value is PromiseLike<unknown> {
13
+ return (
14
+ value !== null &&
15
+ typeof value === "object" &&
16
+ typeof (value as { then?: unknown }).then === "function"
17
+ );
18
+ }