@timber-js/app 0.2.0-alpha.186 → 0.2.0-alpha.188

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 (331) hide show
  1. package/dist/_chunks/{actions-C-Rw9vPc.js → actions-35jnMdeJ.js} +4 -3
  2. package/dist/_chunks/{actions-C-Rw9vPc.js.map → actions-35jnMdeJ.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
  4. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
  5. package/dist/_chunks/{als-slots-mFweg276.js → als-slots-BEEIPKYm.js} +3 -4
  6. package/dist/_chunks/{als-slots-mFweg276.js.map → als-slots-BEEIPKYm.js.map} +1 -1
  7. package/dist/_chunks/{cache-api-Cd0VZ_Pd.js → cache-api-DjNrIWRR.js} +7 -13
  8. package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
  9. package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
  10. package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
  11. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
  12. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
  13. package/dist/_chunks/{cloudflare-Cs0uZXea.js → cloudflare-CGP6BZKO.js} +4 -3
  14. package/dist/_chunks/{cloudflare-Cs0uZXea.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
  15. package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
  16. package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
  17. package/dist/_chunks/{error-boundary-DpYRI_I1.js → error-boundary-D-ODYX41.js} +25 -2
  18. package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
  19. package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
  20. package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
  21. package/dist/_chunks/{logger-N7e5auP0.js → logger-D8xJZXIN.js} +27 -40
  22. package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
  23. package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
  24. package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
  25. package/dist/_chunks/{plugin-context-DEGLSJs3.js → plugin-context-rCinWLiE.js} +7 -2
  26. package/dist/_chunks/{plugin-context-DEGLSJs3.js.map → plugin-context-rCinWLiE.js.map} +1 -1
  27. package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
  28. package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
  29. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
  30. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
  31. package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
  32. package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
  33. package/dist/_chunks/{cli-schema-sync-CKgHC2MB.js → scanner-C8b0Gcw3.js} +5 -298
  34. package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
  35. package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
  36. package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
  37. package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
  38. package/dist/_chunks/{param-value-C8TNYchQ.js → segment-context-CjOlyB8Y.js} +33 -2
  39. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
  40. package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -1
  41. package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
  42. package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
  43. package/dist/_chunks/{navigation-root-B29qg0_T.js → use-segment-params-C4r4BD9T.js} +129 -5
  44. package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
  45. package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
  46. package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
  47. package/dist/adapters/cloudflare-dev.js +1 -1
  48. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  49. package/dist/adapters/cloudflare.d.ts.map +1 -1
  50. package/dist/adapters/cloudflare.js +1 -1
  51. package/dist/adapters/compress-module.d.ts +12 -0
  52. package/dist/adapters/compress-module.d.ts.map +1 -1
  53. package/dist/adapters/nitro.js +54 -2
  54. package/dist/adapters/nitro.js.map +1 -1
  55. package/dist/cache/index.js +1 -1
  56. package/dist/cdn/cloudflare-purge.js +30 -0
  57. package/dist/cdn/cloudflare-purge.js.map +1 -0
  58. package/dist/cdn/fastly-purge.js +33 -0
  59. package/dist/cdn/fastly-purge.js.map +1 -0
  60. package/dist/cdn/index.js +94 -0
  61. package/dist/cdn/index.js.map +1 -0
  62. package/dist/cdn/workers-cache-purge.js +35 -0
  63. package/dist/cdn/workers-cache-purge.js.map +1 -0
  64. package/dist/cli-check.d.ts +153 -0
  65. package/dist/cli-check.d.ts.map +1 -0
  66. package/dist/cli.d.ts +34 -8
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +46 -21
  69. package/dist/cli.js.map +1 -1
  70. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  71. package/dist/client/browser-entry/index.d.ts +1 -1
  72. package/dist/client/browser-entry/index.d.ts.map +1 -1
  73. package/dist/client/error-boundary.d.ts +6 -0
  74. package/dist/client/error-boundary.d.ts.map +1 -1
  75. package/dist/client/error-boundary.js +1 -1
  76. package/dist/client/index.d.ts +1 -0
  77. package/dist/client/index.d.ts.map +1 -1
  78. package/dist/client/index.js +27 -33
  79. package/dist/client/index.js.map +1 -1
  80. package/dist/client/internal.js +33 -17
  81. package/dist/client/internal.js.map +1 -1
  82. package/dist/client/link.d.ts +1 -7
  83. package/dist/client/link.d.ts.map +1 -1
  84. package/dist/client/navigation-commit.d.ts +16 -0
  85. package/dist/client/navigation-commit.d.ts.map +1 -1
  86. package/dist/client/router-pipeline.d.ts.map +1 -1
  87. package/dist/client/router.d.ts.map +1 -1
  88. package/dist/client/rsc-fetch.d.ts +9 -1
  89. package/dist/client/rsc-fetch.d.ts.map +1 -1
  90. package/dist/client/segment-cache.d.ts +14 -0
  91. package/dist/client/segment-cache.d.ts.map +1 -1
  92. package/dist/client/use-query-states.d.ts +9 -3
  93. package/dist/client/use-query-states.d.ts.map +1 -1
  94. package/dist/codec.js +1 -1
  95. package/dist/cookies/define-cookie.d.ts.map +1 -1
  96. package/dist/cookies/index.js +2 -2
  97. package/dist/cookies/index.js.map +1 -1
  98. package/dist/index.d.ts +4 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +142 -477
  101. package/dist/index.js.map +1 -1
  102. package/dist/params/index.js +1 -1
  103. package/dist/plugin-context.d.ts +15 -0
  104. package/dist/plugin-context.d.ts.map +1 -1
  105. package/dist/plugins/routing.d.ts +0 -9
  106. package/dist/plugins/routing.d.ts.map +1 -1
  107. package/dist/plugins/shims.d.ts.map +1 -1
  108. package/dist/plugins/static-build.d.ts +24 -0
  109. package/dist/plugins/static-build.d.ts.map +1 -1
  110. package/dist/routing/codegen-shared.d.ts +3 -44
  111. package/dist/routing/codegen-shared.d.ts.map +1 -1
  112. package/dist/routing/codegen-types.d.ts +10 -31
  113. package/dist/routing/codegen-types.d.ts.map +1 -1
  114. package/dist/routing/codegen-write.d.ts +51 -0
  115. package/dist/routing/codegen-write.d.ts.map +1 -0
  116. package/dist/routing/codegen.d.ts.map +1 -1
  117. package/dist/routing/convention-lint.d.ts +18 -4
  118. package/dist/routing/convention-lint.d.ts.map +1 -1
  119. package/dist/routing/export-detect.d.ts +16 -0
  120. package/dist/routing/export-detect.d.ts.map +1 -1
  121. package/dist/routing/index.js +3 -2
  122. package/dist/routing/link-codegen.d.ts +19 -4
  123. package/dist/routing/link-codegen.d.ts.map +1 -1
  124. package/dist/routing/manifest-codegen.d.ts +1 -7
  125. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  126. package/dist/routing/segment-keys.d.ts +22 -0
  127. package/dist/routing/segment-keys.d.ts.map +1 -1
  128. package/dist/routing/types.d.ts +0 -6
  129. package/dist/routing/types.d.ts.map +1 -1
  130. package/dist/schema-bridge.d.ts +60 -9
  131. package/dist/schema-bridge.d.ts.map +1 -1
  132. package/dist/search-params/define.d.ts +62 -8
  133. package/dist/search-params/define.d.ts.map +1 -1
  134. package/dist/search-params/index.d.ts +0 -1
  135. package/dist/search-params/index.d.ts.map +1 -1
  136. package/dist/search-params/index.js +66 -29
  137. package/dist/search-params/index.js.map +1 -1
  138. package/dist/search-params/parse-total.d.ts +70 -0
  139. package/dist/search-params/parse-total.d.ts.map +1 -0
  140. package/dist/search-params/wrappers.d.ts +26 -3
  141. package/dist/search-params/wrappers.d.ts.map +1 -1
  142. package/dist/server/access-gate.d.ts +19 -8
  143. package/dist/server/access-gate.d.ts.map +1 -1
  144. package/dist/server/action-handler.d.ts.map +1 -1
  145. package/dist/server/als-registry.d.ts +16 -0
  146. package/dist/server/als-registry.d.ts.map +1 -1
  147. package/dist/server/chain-url-parts.d.ts +2 -3
  148. package/dist/server/chain-url-parts.d.ts.map +1 -1
  149. package/dist/server/compress.d.ts.map +1 -1
  150. package/dist/server/deny-boundary.d.ts +148 -15
  151. package/dist/server/deny-boundary.d.ts.map +1 -1
  152. package/dist/server/deny-renderer.d.ts +2 -2
  153. package/dist/server/deny-renderer.d.ts.map +1 -1
  154. package/dist/server/error-boundary-wrapper.d.ts +85 -15
  155. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  156. package/dist/server/index.d.ts +0 -1
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +3 -2
  159. package/dist/server/index.js.map +1 -1
  160. package/dist/server/internal.d.ts +3 -1
  161. package/dist/server/internal.d.ts.map +1 -1
  162. package/dist/server/internal.js +344 -237
  163. package/dist/server/internal.js.map +1 -1
  164. package/dist/server/metadata-collector.d.ts +52 -0
  165. package/dist/server/metadata-collector.d.ts.map +1 -0
  166. package/dist/server/param-coercion.d.ts +12 -5
  167. package/dist/server/param-coercion.d.ts.map +1 -1
  168. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  169. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  170. package/dist/server/pipeline-phases.d.ts.map +1 -1
  171. package/dist/server/primitives.d.ts +23 -0
  172. package/dist/server/primitives.d.ts.map +1 -1
  173. package/dist/server/route-element-builder.d.ts +1 -12
  174. package/dist/server/route-element-builder.d.ts.map +1 -1
  175. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  177. package/dist/server/rsc-entry/error-renderer.d.ts +1 -1
  178. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/helpers.d.ts +0 -7
  180. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  181. package/dist/server/rsc-entry/index.d.ts +0 -1
  182. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  183. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  184. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  185. package/dist/server/rsc-entry/rsc-payload.d.ts +1 -3
  186. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  187. package/dist/server/rsc-entry/rsc-stream.d.ts +12 -0
  188. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  189. package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -2
  190. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  191. package/dist/server/skippable-prefix.d.ts +3 -0
  192. package/dist/server/skippable-prefix.d.ts.map +1 -1
  193. package/dist/server/slot-resolver.d.ts +18 -19
  194. package/dist/server/slot-resolver.d.ts.map +1 -1
  195. package/dist/server/ssr-bridge-types.d.ts +11 -0
  196. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  197. package/dist/server/ssr-entry.d.ts +0 -1
  198. package/dist/server/ssr-entry.d.ts.map +1 -1
  199. package/dist/server/state-tree-diff.d.ts +9 -16
  200. package/dist/server/state-tree-diff.d.ts.map +1 -1
  201. package/dist/server/static-generator.d.ts.map +1 -1
  202. package/dist/server/status-code-resolver.d.ts +8 -1
  203. package/dist/server/status-code-resolver.d.ts.map +1 -1
  204. package/dist/server/tree-builder.d.ts +28 -43
  205. package/dist/server/tree-builder.d.ts.map +1 -1
  206. package/dist/server/types.d.ts +12 -7
  207. package/dist/server/types.d.ts.map +1 -1
  208. package/dist/server/utils/element-type.d.ts +40 -0
  209. package/dist/server/utils/element-type.d.ts.map +1 -0
  210. package/dist/shared/rsc-media-type.d.ts +40 -0
  211. package/dist/shared/rsc-media-type.d.ts.map +1 -0
  212. package/dist/shared/segment-info.d.ts +7 -0
  213. package/dist/shared/segment-info.d.ts.map +1 -1
  214. package/docs/api/30-api-server.mdx +1 -1
  215. package/docs/api/33-api-search-params.mdx +38 -16
  216. package/docs/api/35-api-typescript.mdx +3 -3
  217. package/docs/api/36-cli.mdx +34 -7
  218. package/docs/learn/00-introduction.mdx +1 -1
  219. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  220. package/docs/learn/05-typed-params.mdx +8 -8
  221. package/docs/learn/07-typed-routes.mdx +12 -7
  222. package/docs/learn/11-error-handling.mdx +16 -0
  223. package/docs/more/01-advanced-routing.mdx +1 -1
  224. package/docs/more/03-coming-from-nextjs.mdx +2 -2
  225. package/docs/more/50-ai-agent-instructions.mdx +6 -4
  226. package/package.json +8 -5
  227. package/src/adapters/cloudflare.ts +4 -1
  228. package/src/adapters/compress-module.ts +79 -1
  229. package/src/cli-check.ts +458 -0
  230. package/src/cli.ts +59 -24
  231. package/src/client/browser-entry/action-dispatch.ts +2 -1
  232. package/src/client/browser-entry/index.ts +0 -5
  233. package/src/client/error-boundary.tsx +65 -1
  234. package/src/client/index.ts +14 -3
  235. package/src/client/link.tsx +65 -64
  236. package/src/client/navigation-commit.ts +27 -4
  237. package/src/client/params-context.ts +4 -4
  238. package/src/client/router-pipeline.ts +4 -0
  239. package/src/client/router.ts +1 -0
  240. package/src/client/rsc-fetch.ts +14 -4
  241. package/src/client/segment-cache.ts +26 -5
  242. package/src/client/use-query-states.ts +102 -39
  243. package/src/cookies/define-cookie.ts +6 -1
  244. package/src/index.ts +20 -3
  245. package/src/plugin-context.ts +26 -0
  246. package/src/plugins/routing.ts +84 -146
  247. package/src/plugins/shims.ts +0 -1
  248. package/src/plugins/static-build.ts +78 -24
  249. package/src/routing/codegen-shared.ts +3 -79
  250. package/src/routing/codegen-types.ts +10 -31
  251. package/src/routing/codegen-write.ts +139 -0
  252. package/src/routing/codegen.ts +56 -182
  253. package/src/routing/convention-lint.ts +139 -40
  254. package/src/routing/export-detect.ts +151 -7
  255. package/src/routing/link-codegen.ts +32 -65
  256. package/src/routing/manifest-codegen.ts +1 -59
  257. package/src/routing/scanner.ts +3 -3
  258. package/src/routing/segment-keys.ts +37 -0
  259. package/src/routing/types.ts +0 -6
  260. package/src/schema-bridge.ts +180 -58
  261. package/src/search-params/define.ts +102 -37
  262. package/src/search-params/index.ts +0 -1
  263. package/src/search-params/parse-total.ts +78 -0
  264. package/src/search-params/wrappers.ts +60 -11
  265. package/src/server/access-gate.tsx +61 -68
  266. package/src/server/action-handler.ts +1 -4
  267. package/src/server/als-registry.ts +16 -0
  268. package/src/server/chain-url-parts.ts +2 -3
  269. package/src/server/compress.ts +9 -1
  270. package/src/server/deny-boundary.ts +269 -41
  271. package/src/server/deny-renderer.ts +32 -21
  272. package/src/server/error-boundary-wrapper.ts +166 -79
  273. package/src/server/index.ts +1 -3
  274. package/src/server/internal.ts +2 -2
  275. package/src/server/metadata-collector.ts +115 -0
  276. package/src/server/param-coercion.ts +13 -61
  277. package/src/server/pipeline-helpers.ts +2 -2
  278. package/src/server/pipeline-outcome.ts +2 -1
  279. package/src/server/pipeline-phases.ts +9 -9
  280. package/src/server/primitives.ts +25 -0
  281. package/src/server/route-element-builder.ts +169 -194
  282. package/src/server/rsc-cache-key-guard.ts +2 -8
  283. package/src/server/rsc-entry/deny-fallback.ts +3 -2
  284. package/src/server/rsc-entry/error-renderer.ts +31 -11
  285. package/src/server/rsc-entry/helpers.ts +3 -12
  286. package/src/server/rsc-entry/index.ts +0 -5
  287. package/src/server/rsc-entry/render-route.ts +14 -11
  288. package/src/server/rsc-entry/revalidate-renderer.ts +2 -1
  289. package/src/server/rsc-entry/rsc-payload.ts +104 -20
  290. package/src/server/rsc-entry/rsc-stream.ts +25 -2
  291. package/src/server/rsc-entry/ssr-renderer.ts +28 -16
  292. package/src/server/skippable-prefix.ts +21 -39
  293. package/src/server/slot-resolver.ts +69 -189
  294. package/src/server/ssr-bridge-types.ts +12 -0
  295. package/src/server/ssr-entry.ts +8 -6
  296. package/src/server/state-tree-diff.ts +11 -62
  297. package/src/server/static-generator.ts +3 -2
  298. package/src/server/status-code-resolver.ts +28 -11
  299. package/src/server/tree-builder.ts +35 -228
  300. package/src/server/types.ts +12 -7
  301. package/src/server/utils/element-type.ts +72 -0
  302. package/src/shared/rsc-media-type.ts +43 -0
  303. package/src/shared/segment-info.ts +7 -0
  304. package/dist/_chunks/cache-api-Cd0VZ_Pd.js.map +0 -1
  305. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +0 -1
  306. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +0 -1
  307. package/dist/_chunks/logger-N7e5auP0.js.map +0 -1
  308. package/dist/_chunks/navigation-root-B29qg0_T.js.map +0 -1
  309. package/dist/_chunks/param-value-C8TNYchQ.js.map +0 -1
  310. package/dist/_chunks/registry-DbJPKoBp.js +0 -20
  311. package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
  312. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
  313. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
  314. package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
  315. package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
  316. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
  317. package/dist/_chunks/use-segment-params-ClyUNq4d.js +0 -128
  318. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +0 -1
  319. package/dist/_chunks/walkers-BhhwI9TD.js +0 -936
  320. package/dist/_chunks/walkers-BhhwI9TD.js.map +0 -1
  321. package/dist/search-params/registry.d.ts +0 -20
  322. package/dist/search-params/registry.d.ts.map +0 -1
  323. package/dist/segment-params/define.d.ts +0 -83
  324. package/dist/segment-params/define.d.ts.map +0 -1
  325. package/dist/segment-params/index.d.ts +0 -3
  326. package/dist/segment-params/index.d.ts.map +0 -1
  327. package/dist/segment-params/index.js +0 -70
  328. package/dist/segment-params/index.js.map +0 -1
  329. package/src/search-params/registry.ts +0 -31
  330. package/src/segment-params/define.ts +0 -226
  331. package/src/segment-params/index.ts +0 -9
@@ -401,6 +401,55 @@ interface StaticGenerateModule {
401
401
  ) => Promise<StaticGenerationSummary>;
402
402
  }
403
403
 
404
+ /**
405
+ * A port as Node actually hands it over: `unref()` is a Node addition the DOM
406
+ * lib type does not declare. Optional rather than asserted, so a runtime whose
407
+ * `MessageChannel` has no such method is simply skipped instead of throwing.
408
+ */
409
+ type ReleasablePort = MessagePort & { unref?: () => void };
410
+
411
+ /**
412
+ * Run `fn` with `MessageChannel` instrumented, then stop every channel opened
413
+ * while it ran from holding the process open.
414
+ *
415
+ * Static generation is the only build pass that renders **HTML** in the build
416
+ * process: it imports the built app bundle and drives the full SSR pipeline
417
+ * through it. React's scheduler opens a `MessageChannel` at module scope in
418
+ * that bundle (`scheduler`'s DOM build) and never closes it, and a ref'd
419
+ * `MessagePort` keeps Node's event loop alive on its own — so `timber build`
420
+ * finished the site, printed its summary, and then hung forever. Server-mode
421
+ * builds are unaffected because nothing there renders through react-dom.
422
+ *
423
+ * `unref()` is the whole fix: it leaves the port working and only drops the
424
+ * "keep the process alive" property. It is applied **after** `fn` resolves, so
425
+ * a render in flight can never be cut short by the process exiting under it —
426
+ * an unref'd port would let Node exit while the scheduler still had a
427
+ * continuation queued, which is a silently truncated page rather than a hang.
428
+ *
429
+ * Nothing renders after this pass. If that ever changes, the release has to
430
+ * move to the end of whatever renders last.
431
+ *
432
+ * @internal Exported for testing.
433
+ */
434
+ export async function withSchedulerPortsReleased<T>(fn: () => Promise<T>): Promise<T> {
435
+ const opened: ReleasablePort[] = [];
436
+ const OriginalMessageChannel = globalThis.MessageChannel;
437
+
438
+ globalThis.MessageChannel = class extends OriginalMessageChannel {
439
+ constructor() {
440
+ super();
441
+ opened.push(this.port1, this.port2);
442
+ }
443
+ };
444
+
445
+ try {
446
+ return await fn();
447
+ } finally {
448
+ globalThis.MessageChannel = OriginalMessageChannel;
449
+ for (const port of opened) port.unref?.();
450
+ }
451
+ }
452
+
404
453
  /**
405
454
  * Load the built RSC entry and run the static generation pass.
406
455
  *
@@ -435,26 +484,6 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
435
484
 
436
485
  console.log('[timber] Static generation: building site…');
437
486
 
438
- let mod: StaticGenerateModule;
439
- try {
440
- const entryUrl = `${pathToFileURL(entryPath).href}?t=${Date.now()}`;
441
- mod = (await import(/* @vite-ignore */ entryUrl)) as StaticGenerateModule;
442
- } catch (error) {
443
- throw new Error(
444
- `[timber] Static generation: failed to load the built RSC entry (${entryPath}). ` +
445
- `The full request handler must initialize for static rendering.`,
446
- { cause: error }
447
- );
448
- }
449
-
450
- const generate = mod.__timber_generateStaticSite;
451
- if (typeof generate !== 'function') {
452
- throw new Error(
453
- `[timber] Static generation: built RSC entry does not export ` +
454
- `__timber_generateStaticSite — rebuild from a clean build directory`
455
- );
456
- }
457
-
458
487
  const staticDir = join(ctx.buildDir, 'static-pages');
459
488
  await cleanConditionalArtifacts(staticDir);
460
489
  await mkdir(staticDir, { recursive: true });
@@ -470,10 +499,35 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
470
499
  // now inside the payload itself — a field on its root row — so the `.rsc`
471
500
  // file carries them and there is nothing to lift (TIM-1294).
472
501
 
473
- const summary = await generate({
474
- autoSitemapEnabled,
475
- renderTimeoutMs: ctx.config.renderTimeoutMs,
476
- onEntry: (entry: StaticPageEntry) => writer.write(entry),
502
+ // The app bundle is imported and rendered inside this wrapper, and only
503
+ // inside it: everything it opens to schedule React work is released on the
504
+ // way out so the build process can exit.
505
+ const summary = await withSchedulerPortsReleased(async () => {
506
+ let mod: StaticGenerateModule;
507
+ try {
508
+ const entryUrl = `${pathToFileURL(entryPath).href}?t=${Date.now()}`;
509
+ mod = (await import(/* @vite-ignore */ entryUrl)) as StaticGenerateModule;
510
+ } catch (error) {
511
+ throw new Error(
512
+ `[timber] Static generation: failed to load the built RSC entry (${entryPath}). ` +
513
+ `The full request handler must initialize for static rendering.`,
514
+ { cause: error }
515
+ );
516
+ }
517
+
518
+ const generate = mod.__timber_generateStaticSite;
519
+ if (typeof generate !== 'function') {
520
+ throw new Error(
521
+ `[timber] Static generation: built RSC entry does not export ` +
522
+ `__timber_generateStaticSite — rebuild from a clean build directory`
523
+ );
524
+ }
525
+
526
+ return generate({
527
+ autoSitemapEnabled,
528
+ renderTimeoutMs: ctx.config.renderTimeoutMs,
529
+ onEntry: (entry: StaticPageEntry) => writer.write(entry),
530
+ });
477
531
  });
478
532
 
479
533
  const totalUrls = summary.rendered + summary.errors.length;
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Shared codegen helpers — import-path computation, codec chain type
3
- * builder, and searchParams type formatter.
2
+ * Shared codegen helpers — import-path computation and codec chain type
3
+ * builders.
4
4
  *
5
5
  * Extracted from `codegen.ts` so `link-codegen.ts` can use the same
6
6
  * helpers without a cyclic import.
7
7
  */
8
8
 
9
9
  import { relative, posix } from 'node:path';
10
- import type { ParamEntry, RouteEntry } from './codegen-types.js';
10
+ import type { ParamEntry } from './codegen-types.js';
11
11
 
12
12
  /**
13
13
  * Compute a relative import specifier for a codec/page file, stripping
@@ -21,65 +21,6 @@ export function codecImportPath(codecFilePath: string, importBase: string | unde
21
21
  return './' + posix.basename(absPath);
22
22
  }
23
23
 
24
- /** Name of the shared helper type emitted at the top of the .d.ts. */
25
- export const RESOLVE_SEGMENT_FIELD_TYPE_NAME = '_TimberResolveSegmentField';
26
-
27
- /**
28
- * Helper type emitted once at the top of the generated `.d.ts` and
29
- * referenced by every codec-chain conditional. Without this shared
30
- * helper, the inline expansion would duplicate the fallback branch on
31
- * every step and grow O(2^N) in chain depth (a single deep nested route
32
- * could blow up the file size and TS performance). With the helper,
33
- * each step reuses the named type and growth is O(N).
34
- *
35
- * The helper is a 2-arg conditional: given a typeof import expression
36
- * (`Def`), a key (`K`), and a fallback (`F`), it returns `T[K]` if
37
- * `Def extends ParamsDefinition<T>` and `K extends keyof T`, otherwise
38
- * `F`. The codec chain composes calls to this helper.
39
- */
40
- export function emitResolveSegmentFieldHelper(): string {
41
- return [
42
- `type ${RESOLVE_SEGMENT_FIELD_TYPE_NAME}<Def, K extends string, F> =`,
43
- ` Def extends import('@timber-js/app/segment-params').ParamsDefinition<infer T>`,
44
- ` ? K extends keyof T ? T[K] : F`,
45
- ` : F;`,
46
- ].join('\n');
47
- }
48
-
49
- /**
50
- * Build a TypeScript type expression that resolves a single param's
51
- * codec by walking a chain of params.ts files in priority order.
52
- *
53
- * Each entry in the chain emits one application of the shared
54
- * `_TimberResolveSegmentField` helper type. Composing N applications
55
- * grows linearly with chain depth (O(N) characters), unlike an inline
56
- * conditional that would duplicate the fallback in each branch and
57
- * grow O(2^N).
58
- *
59
- * The closest match (position 0 in the chain) is checked first; if its
60
- * `segmentParams` definition declares the key, its inferred type wins.
61
- * Otherwise we fall through to the next entry, and finally to the
62
- * provided fallback. See TIM-834.
63
- */
64
- export function buildCodecChainType(
65
- p: ParamEntry,
66
- importBase: string | undefined,
67
- fallback: string
68
- ): string {
69
- const files = p.codecFilePaths;
70
- if (!files || files.length === 0) return fallback;
71
- const key = JSON.stringify(p.name);
72
- // Compose helper applications inside-out so the closest entry
73
- // (files[0]) ends up as the OUTERMOST application. Each application
74
- // adds a constant-size wrapper around the running fallback.
75
- let inner = fallback;
76
- for (let i = files.length - 1; i >= 0; i--) {
77
- const importPath = codecImportPath(files[i], importBase);
78
- inner = `${RESOLVE_SEGMENT_FIELD_TYPE_NAME}<(typeof import('${importPath}'))['segmentParams'], ${key}, ${inner}>`;
79
- }
80
- return inner;
81
- }
82
-
83
24
  /**
84
25
  * Build a TypeScript type expression for a single param using the global
85
26
  * schema's InferCodecType. Falls back to the param's base type when
@@ -96,20 +37,3 @@ export function buildSchemaParamType(p: ParamEntry, fallback?: string): string {
96
37
  const fb = fallback ?? p.type;
97
38
  return `typeof schema['segmentParams'] extends Record<${key}, infer V> ? InferCodecType<V> : ${fb}`;
98
39
  }
99
-
100
- /**
101
- * Format the searchParams type for a route entry.
102
- *
103
- * When a page.tsx (or params.ts) exports searchParams, we reference its
104
- * inferred type via an import type. The import path is relative to
105
- * `importBase` (the directory where the .d.ts will be written). When
106
- * importBase is undefined, falls back to a bare relative path.
107
- */
108
- export function formatSearchParamsType(route: RouteEntry, importBase?: string): string {
109
- if (route.hasSearchParams && route.searchParamsPagePath) {
110
- const importPath = codecImportPath(route.searchParamsPagePath, importBase);
111
- // Extract the type from the named 'searchParams' export of the page module.
112
- return `(typeof import('${importPath}'))['searchParams'] extends import('@timber-js/app/search-params').SearchParamsDefinition<infer T> ? T : never`;
113
- }
114
- return '{}';
115
- }
@@ -15,12 +15,18 @@ export interface RouteEntry {
15
15
  segmentPath: string;
16
16
  /** Accumulated params from all ancestor dynamic segments */
17
17
  params: ParamEntry[];
18
- /** Whether the page.tsx exports searchParams */
19
- hasSearchParams: boolean;
20
- /** Absolute path to the page file that exports searchParams (for import paths) */
21
- searchParamsPagePath?: string;
22
18
  /** Whether this is an API route (route.ts) vs page route */
23
19
  isApiRoute: boolean;
20
+ /**
21
+ * Whether a request can reach this segment — it has a `page` or `route`.
22
+ *
23
+ * False for a segment that only hosts a layout/middleware/access above the
24
+ * leaf. Those still need typed `getSegmentParams(SEGMENT_PATH)` overloads
25
+ * (they have a `$segment` module), but must NOT appear in urlPath-keyed
26
+ * emissions — the `Routes` interface and `<Link>` — because their urlPath
27
+ * is not an addressable route. See TIM-1342.
28
+ */
29
+ isRoutable: boolean;
24
30
  /** Whether this entry lives inside a parallel slot (@name). Slot entries
25
31
  * share their parent's urlPath — urlPath-keyed emissions dedupe them,
26
32
  * preferring the non-slot entry. segmentPath-keyed emissions keep all. */
@@ -53,31 +59,4 @@ export interface ParamEntry {
53
59
  * e.g. '[id]', '[...slug]', '[[...slug]]' */
54
60
  bracketName: string;
55
61
  type: 'string' | 'string[]' | 'string[] | undefined';
56
- /**
57
- * Ordered list of params.ts files in the route chain that may declare
58
- * a codec for this param, in priority order — **closest-to-leaf
59
- * first**. The generated type emits a chain that picks the first
60
- * entry whose `segmentParams` definition declares this param key.
61
- *
62
- * This array is the SAME for every param in a given route entry
63
- * (it is the route's full ancestry of params.ts files in leaf-first
64
- * order); the type-level conditional in `buildCodecChainType`
65
- * narrows per-key. Sharing the array matches the runtime semantics
66
- * of `coerceSegmentParams`, which walks segments top-down and lets
67
- * later (deeper) coercions overwrite earlier ones — closest-to-leaf
68
- * wins for both types and runtime.
69
- *
70
- * See TIM-834.
71
- */
72
- codecFilePaths?: string[];
73
- /**
74
- * Per-segment legacy fallback: a layout.tsx or page.tsx that exports
75
- * `segmentParams` directly (pre-TIM-508 convention). Used only when
76
- * the chain has no params.ts files at all. Does NOT participate in
77
- * inheritance — a layout/page export is per-segment only.
78
- *
79
- * @internal Set during `collectRoutes`; consumed at leaf time when
80
- * building the final `codecFilePaths`.
81
- */
82
- legacyCodecFilePath?: string;
83
62
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Route-map codegen writer.
3
+ *
4
+ * Materializes the generated route types on disk: `.timber/timber-routes.d.ts`,
5
+ * `.timber/segment-types.d.ts` (via `writeSegmentTypes`), and the root
6
+ * `timber-env.d.ts` that references them.
7
+ *
8
+ * Lives outside `plugins/routing.ts` because it has two callers with two
9
+ * different failure policies: the dev/build plugin starts the write off the hot
10
+ * path and logs failures, while `timber check` awaits it and treats a failure as
11
+ * fatal — a type check run against absent or stale route types reports errors
12
+ * about code that is fine. One implementation, two policies, so the bytes on
13
+ * disk cannot drift between the two commands.
14
+ *
15
+ * Design docs: 09-typescript.md §"Typed Routes", 18-build-system.md
16
+ */
17
+
18
+ import { writeFile, mkdir } from 'node:fs/promises';
19
+ import { join } from 'node:path';
20
+ import { generateRouteMap } from './codegen.js';
21
+ import { writeSegmentTypes } from './segment-codegen.js';
22
+ import type { RouteTree } from './types.js';
23
+
24
+ /**
25
+ * Path (relative to the project root) of the generated route map declaration
26
+ * file, and the name of the root file that `/// <reference>`s it.
27
+ *
28
+ * Exported because `timber check` has to answer whether the user's tsconfig
29
+ * pulls these into the program — a question about these exact paths. It derives
30
+ * them from here rather than restating them, so a rename cannot leave the check
31
+ * looking for a file nothing writes.
32
+ */
33
+ export const CODEGEN_OUTPUT = '.timber/timber-routes.d.ts';
34
+
35
+ /** Root-level file that references the generated declarations. */
36
+ export const ENV_DTS_FILENAME = 'timber-env.d.ts';
37
+
38
+ /**
39
+ * Content of the timber-env.d.ts file written to the project root.
40
+ *
41
+ * Uses a triple-slash reference so TypeScript auto-discovers the generated
42
+ * route map without the user needing to edit tsconfig.json — same pattern
43
+ * as Next.js's next-env.d.ts.
44
+ *
45
+ * Also references `vite/client` so side-effect imports like
46
+ * `import './globals.css'` type-check without ts(2882), and so
47
+ * `import.meta.env`, `import.meta.hot`, `import.meta.glob`, asset
48
+ * imports (`*.png`, `*.svg`, …), CSS modules, and `?url`/`?raw`/
49
+ * `?inline` query variants all resolve without the user editing
50
+ * tsconfig.json themselves. Next.js's next-env.d.ts plays the same
51
+ * role via `next/types/global`.
52
+ *
53
+ * `vite` is a peer dep of `@timber-js/app`, so the reference always
54
+ * resolves in a correctly-installed project.
55
+ */
56
+ const TIMBER_ENV_DTS = [
57
+ '// This file is auto-generated by timber.js. Do not edit.',
58
+ '// It ensures TypeScript picks up the generated route types and',
59
+ '// Vite-provided ambient types (CSS, assets, import.meta.env, HMR).',
60
+ '/* eslint-disable @typescript-eslint/triple-slash-reference */',
61
+ '/// <reference path=".timber/timber-routes.d.ts" />',
62
+ '/// <reference types="vite/client" />',
63
+ '',
64
+ ].join('\n');
65
+
66
+ /**
67
+ * Codegen writes that have been started but have not yet landed on disk.
68
+ *
69
+ * `writeRouteCodegen` is deliberately unawaited on the dev/build hot path,
70
+ * which leaves that caller no way to know the files exist yet. Anything that
71
+ * tears down the project root — a build process exiting, a test removing its
72
+ * temp root — then races the writes: `rmSync` empties a directory that a
73
+ * pending `mkdir` immediately recreates, and the removal fails with ENOTEMPTY.
74
+ *
75
+ * Every started write registers here and deregisters when it settles, so
76
+ * `flushCodegenWrites()` can await them instead of guessing at a delay.
77
+ */
78
+ const pendingCodegenWrites = new Set<Promise<void>>();
79
+
80
+ /**
81
+ * Resolve once every codegen write started so far has landed on disk.
82
+ *
83
+ * Safe to call at any time and from anywhere — writes are tracked per process,
84
+ * not per plugin instance, so a caller does not need a handle on the plugin
85
+ * that started them. Never rejects: the tracked copy of each write has its
86
+ * rejection swallowed, so a failing write cannot turn a teardown barrier into
87
+ * an unhandled rejection. Callers that care about failure await the promise
88
+ * `writeRouteCodegen` returned instead.
89
+ */
90
+ export async function flushCodegenWrites(): Promise<void> {
91
+ // Loop rather than a single Promise.all: a write started while we were
92
+ // awaiting would otherwise be missed, and the set would still be non-empty
93
+ // on return.
94
+ //
95
+ // Each round drops what it awaited rather than trusting the per-write
96
+ // `finally` to have run, so the two removal paths are independent — a
97
+ // deregistration that stopped firing would leak memory in dev, but it can
98
+ // never turn this into a spin.
99
+ while (pendingCodegenWrites.size > 0) {
100
+ const inFlight = [...pendingCodegenWrites];
101
+ await Promise.all(inFlight);
102
+ for (const write of inFlight) pendingCodegenWrites.delete(write);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Write the generated route map, segment types, and the timber-env.d.ts
108
+ * reference file for `tree`.
109
+ *
110
+ * Returns a promise that **rejects** if any write fails. The dev/build plugin
111
+ * does not await it (types are a developer convenience there, not a runtime
112
+ * dependency) and logs instead; `timber check` awaits it and fails the command.
113
+ * Either way the write is registered with `flushCodegenWrites()`.
114
+ */
115
+ export function writeRouteCodegen(tree: RouteTree, appDir: string, root: string): Promise<void> {
116
+ const timberDir = join(root, '.timber');
117
+ const content = generateRouteMap(tree, { appDir, outputDir: timberDir });
118
+ const routesPath = join(root, CODEGEN_OUTPUT);
119
+ const envPath = join(root, ENV_DTS_FILENAME);
120
+
121
+ const done = mkdir(timberDir, { recursive: true })
122
+ .then(() =>
123
+ Promise.all([
124
+ writeFile(routesPath, content, 'utf-8'),
125
+ writeFile(envPath, TIMBER_ENV_DTS, 'utf-8'),
126
+ writeSegmentTypes(tree, appDir, root),
127
+ ])
128
+ )
129
+ .then(() => undefined);
130
+
131
+ // The tracked copy swallows rejection so `flushCodegenWrites` — a teardown
132
+ // barrier — never rejects and never leaves an unhandled rejection behind.
133
+ // The returned promise keeps its rejection for callers that want it.
134
+ const tracked = done.catch(() => undefined);
135
+ pendingCodegenWrites.add(tracked);
136
+ void tracked.finally(() => pendingCodegenWrites.delete(tracked));
137
+
138
+ return done;
139
+ }