@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71

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 (500) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
  3. package/dist/_chunks/als-registry-BJARkOcu.js.map +1 -0
  4. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  5. package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-CGuYoRHU.js +199 -0
  8. package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
  9. package/dist/_chunks/define-Dz1bqwaS.js +106 -0
  10. package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
  11. package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
  12. package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
  13. package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
  14. package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
  16. package/dist/_chunks/format-Rn922VH2.js.map +1 -0
  17. package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
  18. package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
  19. package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
  20. package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
  21. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
  22. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
  23. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
  24. package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
  25. package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
  26. package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
  27. package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
  28. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
  29. package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
  30. package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
  31. package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
  32. package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
  33. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
  34. package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
  35. package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
  36. package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
  37. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  38. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  39. package/dist/adapters/cloudflare-dev.js +73 -0
  40. package/dist/adapters/cloudflare-dev.js.map +1 -0
  41. package/dist/adapters/cloudflare.d.ts +148 -12
  42. package/dist/adapters/cloudflare.d.ts.map +1 -1
  43. package/dist/adapters/cloudflare.js +135 -11
  44. package/dist/adapters/cloudflare.js.map +1 -1
  45. package/dist/adapters/compress-module.d.ts.map +1 -1
  46. package/dist/adapters/nitro.d.ts +17 -1
  47. package/dist/adapters/nitro.d.ts.map +1 -1
  48. package/dist/adapters/nitro.js +56 -13
  49. package/dist/adapters/nitro.js.map +1 -1
  50. package/dist/cache/cache-api.d.ts +24 -0
  51. package/dist/cache/cache-api.d.ts.map +1 -0
  52. package/dist/cache/fast-hash.d.ts +22 -0
  53. package/dist/cache/fast-hash.d.ts.map +1 -0
  54. package/dist/cache/handler-store.d.ts +31 -0
  55. package/dist/cache/handler-store.d.ts.map +1 -0
  56. package/dist/cache/index.d.ts +7 -5
  57. package/dist/cache/index.d.ts.map +1 -1
  58. package/dist/cache/index.js +111 -73
  59. package/dist/cache/index.js.map +1 -1
  60. package/dist/cache/singleflight.d.ts +18 -1
  61. package/dist/cache/singleflight.d.ts.map +1 -1
  62. package/dist/cache/timber-cache.d.ts +1 -1
  63. package/dist/cache/timber-cache.d.ts.map +1 -1
  64. package/dist/client/error-boundary.d.ts +12 -5
  65. package/dist/client/error-boundary.d.ts.map +1 -1
  66. package/dist/client/error-boundary.js +1 -125
  67. package/dist/client/error-reconstituter.d.ts +54 -0
  68. package/dist/client/error-reconstituter.d.ts.map +1 -0
  69. package/dist/client/form.d.ts +2 -2
  70. package/dist/client/form.d.ts.map +1 -1
  71. package/dist/client/history.d.ts +19 -4
  72. package/dist/client/history.d.ts.map +1 -1
  73. package/dist/client/index.d.ts +6 -5
  74. package/dist/client/index.d.ts.map +1 -1
  75. package/dist/client/index.js +537 -166
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/link-pending-store.d.ts +78 -0
  78. package/dist/client/link-pending-store.d.ts.map +1 -0
  79. package/dist/client/link.d.ts +90 -32
  80. package/dist/client/link.d.ts.map +1 -1
  81. package/dist/client/nav-link-store.d.ts +36 -0
  82. package/dist/client/nav-link-store.d.ts.map +1 -0
  83. package/dist/client/navigation-api-types.d.ts +90 -0
  84. package/dist/client/navigation-api-types.d.ts.map +1 -0
  85. package/dist/client/navigation-api.d.ts +115 -0
  86. package/dist/client/navigation-api.d.ts.map +1 -0
  87. package/dist/client/navigation-context.d.ts +13 -2
  88. package/dist/client/navigation-context.d.ts.map +1 -1
  89. package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
  90. package/dist/client/navigation-root.d.ts.map +1 -0
  91. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  92. package/dist/client/router.d.ts +70 -4
  93. package/dist/client/router.d.ts.map +1 -1
  94. package/dist/client/rsc-fetch.d.ts +38 -3
  95. package/dist/client/rsc-fetch.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +1 -1
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts +1 -1
  99. package/dist/client/segment-context.d.ts.map +1 -1
  100. package/dist/client/segment-merger.d.ts.map +1 -1
  101. package/dist/client/segment-outlet.d.ts +63 -0
  102. package/dist/client/segment-outlet.d.ts.map +1 -0
  103. package/dist/client/ssr-data.d.ts +13 -4
  104. package/dist/client/ssr-data.d.ts.map +1 -1
  105. package/dist/client/stale-reload.d.ts +15 -0
  106. package/dist/client/stale-reload.d.ts.map +1 -1
  107. package/dist/client/top-loader.d.ts +3 -3
  108. package/dist/client/top-loader.d.ts.map +1 -1
  109. package/dist/client/use-params.d.ts +6 -4
  110. package/dist/client/use-params.d.ts.map +1 -1
  111. package/dist/client/use-query-states.d.ts +1 -1
  112. package/dist/client/use-query-states.d.ts.map +1 -1
  113. package/dist/codec.d.ts +23 -0
  114. package/dist/codec.d.ts.map +1 -0
  115. package/dist/codec.js +2 -0
  116. package/dist/cookies/define-cookie.d.ts +35 -14
  117. package/dist/cookies/define-cookie.d.ts.map +1 -1
  118. package/dist/cookies/index.d.ts +2 -0
  119. package/dist/cookies/index.d.ts.map +1 -1
  120. package/dist/cookies/index.js +3 -84
  121. package/dist/fonts/css.d.ts +1 -0
  122. package/dist/fonts/css.d.ts.map +1 -1
  123. package/dist/index.d.ts +154 -38
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +12092 -11916
  126. package/dist/index.js.map +1 -1
  127. package/dist/plugins/adapter-build.d.ts +1 -1
  128. package/dist/plugins/adapter-build.d.ts.map +1 -1
  129. package/dist/plugins/build-manifest.d.ts +2 -2
  130. package/dist/plugins/build-manifest.d.ts.map +1 -1
  131. package/dist/plugins/build-report.d.ts +3 -3
  132. package/dist/plugins/build-report.d.ts.map +1 -1
  133. package/dist/plugins/client-chunks.d.ts +32 -0
  134. package/dist/plugins/client-chunks.d.ts.map +1 -0
  135. package/dist/plugins/content.d.ts +1 -1
  136. package/dist/plugins/content.d.ts.map +1 -1
  137. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  138. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  139. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  140. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  141. package/dist/plugins/dev-logs.d.ts +1 -1
  142. package/dist/plugins/dev-logs.d.ts.map +1 -1
  143. package/dist/plugins/dev-server.d.ts +1 -1
  144. package/dist/plugins/dev-server.d.ts.map +1 -1
  145. package/dist/plugins/entries.d.ts +1 -1
  146. package/dist/plugins/entries.d.ts.map +1 -1
  147. package/dist/plugins/fonts.d.ts +19 -5
  148. package/dist/plugins/fonts.d.ts.map +1 -1
  149. package/dist/plugins/mdx.d.ts +1 -1
  150. package/dist/plugins/mdx.d.ts.map +1 -1
  151. package/dist/plugins/routing.d.ts +1 -1
  152. package/dist/plugins/routing.d.ts.map +1 -1
  153. package/dist/plugins/server-bundle.d.ts.map +1 -1
  154. package/dist/plugins/shims.d.ts +6 -5
  155. package/dist/plugins/shims.d.ts.map +1 -1
  156. package/dist/plugins/static-build.d.ts +1 -1
  157. package/dist/plugins/static-build.d.ts.map +1 -1
  158. package/dist/routing/codegen.d.ts +2 -2
  159. package/dist/routing/codegen.d.ts.map +1 -1
  160. package/dist/routing/index.d.ts +2 -0
  161. package/dist/routing/index.d.ts.map +1 -1
  162. package/dist/routing/index.js +3 -2
  163. package/dist/routing/scanner.d.ts.map +1 -1
  164. package/dist/routing/segment-classify.d.ts +46 -0
  165. package/dist/routing/segment-classify.d.ts.map +1 -0
  166. package/dist/routing/status-file-lint.d.ts +2 -1
  167. package/dist/routing/status-file-lint.d.ts.map +1 -1
  168. package/dist/routing/types.d.ts +16 -4
  169. package/dist/routing/types.d.ts.map +1 -1
  170. package/dist/rsc-runtime/rsc.d.ts +1 -1
  171. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  172. package/dist/rsc-runtime/ssr.d.ts +12 -0
  173. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  174. package/dist/schema-bridge.d.ts +76 -0
  175. package/dist/schema-bridge.d.ts.map +1 -0
  176. package/dist/search-params/define.d.ts +139 -0
  177. package/dist/search-params/define.d.ts.map +1 -0
  178. package/dist/search-params/index.d.ts +4 -6
  179. package/dist/search-params/index.d.ts.map +1 -1
  180. package/dist/search-params/index.js +4 -474
  181. package/dist/search-params/registry.d.ts +1 -1
  182. package/dist/search-params/wrappers.d.ts +53 -0
  183. package/dist/search-params/wrappers.d.ts.map +1 -0
  184. package/dist/segment-params/define.d.ts +78 -0
  185. package/dist/segment-params/define.d.ts.map +1 -0
  186. package/dist/segment-params/index.d.ts +7 -0
  187. package/dist/segment-params/index.d.ts.map +1 -0
  188. package/dist/segment-params/index.js +4 -0
  189. package/dist/server/access-gate.d.ts +4 -0
  190. package/dist/server/access-gate.d.ts.map +1 -1
  191. package/dist/server/action-client.d.ts +12 -1
  192. package/dist/server/action-client.d.ts.map +1 -1
  193. package/dist/server/action-encryption.d.ts +76 -0
  194. package/dist/server/action-encryption.d.ts.map +1 -0
  195. package/dist/server/action-handler.d.ts.map +1 -1
  196. package/dist/server/actions.d.ts +3 -6
  197. package/dist/server/actions.d.ts.map +1 -1
  198. package/dist/server/als-registry.d.ts +32 -4
  199. package/dist/server/als-registry.d.ts.map +1 -1
  200. package/dist/server/build-manifest.d.ts +2 -2
  201. package/dist/server/build-manifest.d.ts.map +1 -1
  202. package/dist/server/debug.d.ts +1 -1
  203. package/dist/server/default-logger.d.ts +22 -0
  204. package/dist/server/default-logger.d.ts.map +1 -0
  205. package/dist/server/deny-page-resolver.d.ts +52 -0
  206. package/dist/server/deny-page-resolver.d.ts.map +1 -0
  207. package/dist/server/deny-renderer.d.ts.map +1 -1
  208. package/dist/server/dev-warnings.d.ts +0 -14
  209. package/dist/server/dev-warnings.d.ts.map +1 -1
  210. package/dist/server/early-hints.d.ts +13 -5
  211. package/dist/server/early-hints.d.ts.map +1 -1
  212. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  213. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  214. package/dist/server/fallback-error.d.ts +4 -3
  215. package/dist/server/fallback-error.d.ts.map +1 -1
  216. package/dist/server/flight-injection-state.d.ts +66 -0
  217. package/dist/server/flight-injection-state.d.ts.map +1 -0
  218. package/dist/server/flight-scripts.d.ts +42 -0
  219. package/dist/server/flight-scripts.d.ts.map +1 -0
  220. package/dist/server/flush.d.ts.map +1 -1
  221. package/dist/server/form-data.d.ts +29 -0
  222. package/dist/server/form-data.d.ts.map +1 -1
  223. package/dist/server/html-injectors.d.ts +51 -11
  224. package/dist/server/html-injectors.d.ts.map +1 -1
  225. package/dist/server/index.d.ts +5 -3
  226. package/dist/server/index.d.ts.map +1 -1
  227. package/dist/server/index.js +2176 -1663
  228. package/dist/server/index.js.map +1 -1
  229. package/dist/server/logger.d.ts +25 -7
  230. package/dist/server/logger.d.ts.map +1 -1
  231. package/dist/server/middleware-runner.d.ts +19 -4
  232. package/dist/server/middleware-runner.d.ts.map +1 -1
  233. package/dist/server/node-stream-transforms.d.ts +113 -0
  234. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  235. package/dist/server/page-deny-boundary.d.ts +31 -0
  236. package/dist/server/page-deny-boundary.d.ts.map +1 -0
  237. package/dist/server/pipeline-interception.d.ts +1 -1
  238. package/dist/server/pipeline-interception.d.ts.map +1 -1
  239. package/dist/server/pipeline-metadata.d.ts +6 -0
  240. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  241. package/dist/server/pipeline.d.ts +32 -10
  242. package/dist/server/pipeline.d.ts.map +1 -1
  243. package/dist/server/primitives.d.ts +30 -3
  244. package/dist/server/primitives.d.ts.map +1 -1
  245. package/dist/server/render-timeout.d.ts +51 -0
  246. package/dist/server/render-timeout.d.ts.map +1 -0
  247. package/dist/server/request-context.d.ts +76 -37
  248. package/dist/server/request-context.d.ts.map +1 -1
  249. package/dist/server/route-element-builder.d.ts +27 -1
  250. package/dist/server/route-element-builder.d.ts.map +1 -1
  251. package/dist/server/route-handler.d.ts.map +1 -1
  252. package/dist/server/route-matcher.d.ts +9 -2
  253. package/dist/server/route-matcher.d.ts.map +1 -1
  254. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  255. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  256. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  257. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  258. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  259. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  260. package/dist/server/rsc-entry/index.d.ts +8 -3
  261. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  262. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  263. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  264. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  265. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  266. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  267. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  268. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  269. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  270. package/dist/server/safe-load.d.ts +46 -0
  271. package/dist/server/safe-load.d.ts.map +1 -0
  272. package/dist/server/sitemap-generator.d.ts +129 -0
  273. package/dist/server/sitemap-generator.d.ts.map +1 -0
  274. package/dist/server/sitemap-handler.d.ts +22 -0
  275. package/dist/server/sitemap-handler.d.ts.map +1 -0
  276. package/dist/server/slot-resolver.d.ts +1 -1
  277. package/dist/server/slot-resolver.d.ts.map +1 -1
  278. package/dist/server/ssr-entry.d.ts +22 -0
  279. package/dist/server/ssr-entry.d.ts.map +1 -1
  280. package/dist/server/ssr-render.d.ts +39 -21
  281. package/dist/server/ssr-render.d.ts.map +1 -1
  282. package/dist/server/ssr-wrappers.d.ts +50 -0
  283. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  284. package/dist/server/status-code-resolver.d.ts +1 -1
  285. package/dist/server/status-code-resolver.d.ts.map +1 -1
  286. package/dist/server/stream-utils.d.ts +36 -0
  287. package/dist/server/stream-utils.d.ts.map +1 -0
  288. package/dist/server/tracing.d.ts +10 -0
  289. package/dist/server/tracing.d.ts.map +1 -1
  290. package/dist/server/tree-builder.d.ts +22 -19
  291. package/dist/server/tree-builder.d.ts.map +1 -1
  292. package/dist/server/types.d.ts +1 -4
  293. package/dist/server/types.d.ts.map +1 -1
  294. package/dist/server/version-skew.d.ts +61 -0
  295. package/dist/server/version-skew.d.ts.map +1 -0
  296. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  297. package/dist/shared/merge-search-params.d.ts +22 -0
  298. package/dist/shared/merge-search-params.d.ts.map +1 -0
  299. package/dist/shims/font-google.d.ts +1 -1
  300. package/dist/shims/font-google.d.ts.map +1 -1
  301. package/dist/shims/font-google.js +42 -0
  302. package/dist/shims/font-google.js.map +1 -0
  303. package/dist/shims/font-local.d.ts +26 -0
  304. package/dist/shims/font-local.d.ts.map +1 -0
  305. package/dist/shims/font-local.js +20 -0
  306. package/dist/shims/font-local.js.map +1 -0
  307. package/dist/shims/navigation-client.d.ts +1 -1
  308. package/dist/shims/navigation-client.d.ts.map +1 -1
  309. package/dist/shims/navigation.d.ts +1 -1
  310. package/dist/shims/navigation.d.ts.map +1 -1
  311. package/dist/utils/directive-parser.d.ts +5 -2
  312. package/dist/utils/directive-parser.d.ts.map +1 -1
  313. package/dist/utils/state-machine.d.ts +80 -0
  314. package/dist/utils/state-machine.d.ts.map +1 -0
  315. package/package.json +37 -17
  316. package/src/adapters/cloudflare-dev.ts +177 -0
  317. package/src/adapters/cloudflare.ts +342 -28
  318. package/src/adapters/compress-module.ts +24 -4
  319. package/src/adapters/nitro.ts +58 -9
  320. package/src/adapters/wrangler.d.ts +7 -0
  321. package/src/cache/cache-api.ts +38 -0
  322. package/src/cache/fast-hash.ts +34 -0
  323. package/src/cache/handler-store.ts +68 -0
  324. package/src/cache/index.ts +9 -5
  325. package/src/cache/singleflight.ts +62 -4
  326. package/src/cache/timber-cache.ts +40 -29
  327. package/src/cli.ts +0 -0
  328. package/src/client/browser-entry.ts +314 -142
  329. package/src/client/error-boundary.tsx +48 -16
  330. package/src/client/error-reconstituter.tsx +65 -0
  331. package/src/client/form.tsx +2 -2
  332. package/src/client/history.ts +26 -4
  333. package/src/client/index.ts +13 -4
  334. package/src/client/link-pending-store.ts +136 -0
  335. package/src/client/link.tsx +346 -105
  336. package/src/client/nav-link-store.ts +47 -0
  337. package/src/client/navigation-api-types.ts +112 -0
  338. package/src/client/navigation-api.ts +332 -0
  339. package/src/client/navigation-context.ts +27 -6
  340. package/src/client/navigation-root.tsx +346 -0
  341. package/src/client/nuqs-adapter.tsx +16 -3
  342. package/src/client/router.ts +302 -77
  343. package/src/client/rsc-fetch.ts +93 -5
  344. package/src/client/segment-cache.ts +1 -1
  345. package/src/client/segment-context.ts +6 -1
  346. package/src/client/segment-merger.ts +2 -8
  347. package/src/client/segment-outlet.tsx +86 -0
  348. package/src/client/ssr-data.ts +13 -5
  349. package/src/client/stale-reload.ts +73 -6
  350. package/src/client/top-loader.tsx +22 -13
  351. package/src/client/use-navigation-pending.ts +1 -1
  352. package/src/client/use-params.ts +7 -5
  353. package/src/client/use-query-states.ts +2 -2
  354. package/src/codec.ts +34 -0
  355. package/src/cookies/define-cookie.ts +72 -21
  356. package/src/cookies/index.ts +7 -0
  357. package/src/fonts/css.ts +2 -1
  358. package/src/index.ts +328 -92
  359. package/src/plugins/adapter-build.ts +8 -2
  360. package/src/plugins/build-manifest.ts +13 -2
  361. package/src/plugins/build-report.ts +3 -3
  362. package/src/plugins/client-chunks.ts +65 -0
  363. package/src/plugins/content.ts +1 -1
  364. package/src/plugins/dev-browser-logs.ts +288 -0
  365. package/src/plugins/dev-error-overlay.ts +70 -1
  366. package/src/plugins/dev-logs.ts +1 -1
  367. package/src/plugins/dev-server.ts +55 -9
  368. package/src/plugins/entries.ts +70 -9
  369. package/src/plugins/fonts.ts +167 -61
  370. package/src/plugins/mdx.ts +1 -1
  371. package/src/plugins/routing.ts +57 -17
  372. package/src/plugins/server-action-exports.ts +1 -1
  373. package/src/plugins/server-bundle.ts +32 -1
  374. package/src/plugins/shims.ts +76 -33
  375. package/src/plugins/static-build.ts +10 -6
  376. package/src/routing/codegen.ts +165 -105
  377. package/src/routing/index.ts +2 -0
  378. package/src/routing/scanner.ts +93 -23
  379. package/src/routing/segment-classify.ts +89 -0
  380. package/src/routing/status-file-lint.ts +3 -2
  381. package/src/routing/types.ts +17 -4
  382. package/src/rsc-runtime/rsc.ts +2 -0
  383. package/src/rsc-runtime/ssr.ts +50 -0
  384. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  385. package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
  386. package/src/search-params/define.ts +482 -0
  387. package/src/search-params/index.ts +13 -19
  388. package/src/search-params/registry.ts +1 -1
  389. package/src/search-params/wrappers.ts +85 -0
  390. package/src/segment-params/define.ts +279 -0
  391. package/src/segment-params/index.ts +28 -0
  392. package/src/server/access-gate.tsx +70 -29
  393. package/src/server/action-client.ts +28 -3
  394. package/src/server/action-encryption.ts +144 -0
  395. package/src/server/action-handler.ts +20 -3
  396. package/src/server/actions.ts +10 -9
  397. package/src/server/als-registry.ts +32 -4
  398. package/src/server/build-manifest.ts +10 -4
  399. package/src/server/compress.ts +25 -7
  400. package/src/server/debug.ts +1 -1
  401. package/src/server/default-logger.ts +99 -0
  402. package/src/server/deny-page-resolver.ts +154 -0
  403. package/src/server/deny-renderer.ts +24 -38
  404. package/src/server/dev-warnings.ts +2 -28
  405. package/src/server/early-hints.ts +36 -15
  406. package/src/server/error-boundary-wrapper.ts +74 -22
  407. package/src/server/fallback-error.ts +31 -15
  408. package/src/server/flight-injection-state.ts +113 -0
  409. package/src/server/flight-scripts.ts +62 -0
  410. package/src/server/flush.ts +2 -1
  411. package/src/server/form-data.ts +76 -0
  412. package/src/server/html-injectors.ts +277 -117
  413. package/src/server/index.ts +9 -5
  414. package/src/server/logger.ts +44 -36
  415. package/src/server/middleware-runner.ts +31 -4
  416. package/src/server/node-stream-transforms.ts +509 -0
  417. package/src/server/page-deny-boundary.tsx +56 -0
  418. package/src/server/pipeline-interception.ts +17 -16
  419. package/src/server/pipeline-metadata.ts +13 -0
  420. package/src/server/pipeline.ts +195 -51
  421. package/src/server/primitives.ts +47 -5
  422. package/src/server/render-timeout.ts +108 -0
  423. package/src/server/request-context.ts +240 -117
  424. package/src/server/route-element-builder.ts +284 -197
  425. package/src/server/route-handler.ts +24 -4
  426. package/src/server/route-matcher.ts +24 -20
  427. package/src/server/rsc-entry/api-handler.ts +15 -16
  428. package/src/server/rsc-entry/error-renderer.ts +300 -89
  429. package/src/server/rsc-entry/helpers.ts +134 -5
  430. package/src/server/rsc-entry/index.ts +202 -113
  431. package/src/server/rsc-entry/rsc-payload.ts +100 -21
  432. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  433. package/src/server/rsc-entry/ssr-bridge.ts +14 -5
  434. package/src/server/rsc-entry/ssr-renderer.ts +173 -40
  435. package/src/server/safe-load.ts +60 -0
  436. package/src/server/sitemap-generator.ts +338 -0
  437. package/src/server/sitemap-handler.ts +126 -0
  438. package/src/server/slot-resolver.ts +243 -228
  439. package/src/server/ssr-entry.ts +211 -32
  440. package/src/server/ssr-render.ts +289 -67
  441. package/src/server/ssr-wrappers.tsx +139 -0
  442. package/src/server/status-code-resolver.ts +1 -1
  443. package/src/server/stream-utils.ts +213 -0
  444. package/src/server/tracing.ts +37 -3
  445. package/src/server/tree-builder.ts +92 -58
  446. package/src/server/types.ts +3 -6
  447. package/src/server/version-skew.ts +104 -0
  448. package/src/server/waituntil-bridge.ts +4 -1
  449. package/src/shared/merge-search-params.ts +55 -0
  450. package/src/shims/font-google.ts +1 -1
  451. package/src/shims/font-local.ts +34 -0
  452. package/src/shims/navigation-client.ts +1 -1
  453. package/src/shims/navigation.ts +2 -1
  454. package/src/utils/directive-parser.ts +5 -2
  455. package/src/utils/state-machine.ts +111 -0
  456. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  457. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  458. package/dist/_chunks/format-DviM89f0.js.map +0 -1
  459. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  460. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  461. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  462. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  463. package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
  464. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  465. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  466. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  467. package/dist/cache/register-cached-function.d.ts +0 -17
  468. package/dist/cache/register-cached-function.d.ts.map +0 -1
  469. package/dist/client/error-boundary.js.map +0 -1
  470. package/dist/client/link-status-provider.d.ts +0 -11
  471. package/dist/client/link-status-provider.d.ts.map +0 -1
  472. package/dist/client/transition-root.d.ts.map +0 -1
  473. package/dist/cookies/index.js.map +0 -1
  474. package/dist/plugins/cache-transform.d.ts +0 -36
  475. package/dist/plugins/cache-transform.d.ts.map +0 -1
  476. package/dist/plugins/dynamic-transform.d.ts +0 -72
  477. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  478. package/dist/search-params/analyze.d.ts +0 -54
  479. package/dist/search-params/analyze.d.ts.map +0 -1
  480. package/dist/search-params/builtin-codecs.d.ts +0 -105
  481. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  482. package/dist/search-params/codecs.d.ts +0 -53
  483. package/dist/search-params/codecs.d.ts.map +0 -1
  484. package/dist/search-params/create.d.ts +0 -106
  485. package/dist/search-params/create.d.ts.map +0 -1
  486. package/dist/search-params/index.js.map +0 -1
  487. package/dist/server/prerender.d.ts +0 -77
  488. package/dist/server/prerender.d.ts.map +0 -1
  489. package/dist/server/response-cache.d.ts +0 -53
  490. package/dist/server/response-cache.d.ts.map +0 -1
  491. package/src/cache/register-cached-function.ts +0 -99
  492. package/src/client/link-status-provider.tsx +0 -30
  493. package/src/client/transition-root.tsx +0 -160
  494. package/src/plugins/cache-transform.ts +0 -199
  495. package/src/plugins/dynamic-transform.ts +0 -161
  496. package/src/search-params/analyze.ts +0 -192
  497. package/src/search-params/builtin-codecs.ts +0 -228
  498. package/src/search-params/create.ts +0 -321
  499. package/src/server/prerender.ts +0 -139
  500. package/src/server/response-cache.ts +0 -277
@@ -0,0 +1,279 @@
1
+ /**
2
+ * defineSegmentParams — factory for typed route param coercion.
3
+ *
4
+ * Creates a ParamsDefinition that coerces raw string params from the
5
+ * URL into typed values. Used by exporting from params.ts (segment-level)
6
+ * convention file.
7
+ *
8
+ * Reuses the shared Codec<T> protocol with Standard Schema auto-detection,
9
+ * same pattern as defineSearchParams. Runtime constraints are stricter:
10
+ * - serialize must return string (not null — path segments can't be omitted)
11
+ * - parse throwing → 404 (invalid param value)
12
+ *
13
+ * Design doc: design/07a-route-params-triage.md
14
+ */
15
+
16
+ import type { Codec } from '../codec.js';
17
+ import {
18
+ type StandardSchemaV1,
19
+ validateSync,
20
+ isStandardSchema,
21
+ isCodec,
22
+ } from '../schema-bridge.js';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Server-only ALS reference for .load()
26
+ // ---------------------------------------------------------------------------
27
+
28
+ // Same pattern as search-params: eagerly registered at server startup
29
+ // to avoid dynamic imports that lose ALS context. See TIM-523.
30
+ let _rawSegmentParams: (() => Promise<Record<string, string | string[]>>) | undefined;
31
+
32
+ /**
33
+ * Register the rawSegmentParams function. Called once at module load time
34
+ * from request-context.ts to avoid dynamic import at call time.
35
+ * @internal
36
+ */
37
+ export function _setRawSegmentParamsFn(fn: () => Promise<Record<string, string | string[]>>): void {
38
+ _rawSegmentParams = fn;
39
+ }
40
+
41
+ function getRawSegmentParams(): Promise<Record<string, string | string[]>> {
42
+ if (!_rawSegmentParams) {
43
+ throw new Error(
44
+ '[timber] segmentParams.load() is only available on the server. ' +
45
+ 'Use useSegmentParams() on the client.'
46
+ );
47
+ }
48
+ return _rawSegmentParams();
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Types
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /** Infer the output type from a Codec or StandardSchemaV1. */
56
+ export type InferParamField<V> =
57
+ V extends Codec<infer T> ? T : V extends StandardSchemaV1<infer T> ? T : never;
58
+
59
+ /** Acceptable field value for defineParams: a Codec or a Standard Schema. */
60
+ export type ParamField<T = unknown> = Codec<T> | StandardSchemaV1<T>;
61
+
62
+ export type { StandardSchemaV1 };
63
+
64
+ /**
65
+ * A typed route params definition.
66
+ *
67
+ * Returned by defineParams(). Provides parse (string → typed) and
68
+ * serialize (typed → string) for each declared param.
69
+ */
70
+ export interface ParamsDefinition<T extends Record<string, unknown>> {
71
+ /** Parse raw string params into typed values. Throws on invalid values. */
72
+ parse(raw: Record<string, string | string[]>): T;
73
+
74
+ /** Serialize typed values back to strings for URL construction. */
75
+ serialize(values: T): Record<string, string>;
76
+
77
+ /**
78
+ * Load typed segment params from the current request context (ALS).
79
+ *
80
+ * Server-only. Reads rawSegmentParams() from ALS and coerces through
81
+ * this definition's codecs, returning fully typed params.
82
+ *
83
+ * ```ts
84
+ * // app/products/[id]/params.ts
85
+ * export const segmentParams = defineSegmentParams({ id: z.coerce.number() })
86
+ *
87
+ * // app/products/[id]/page.tsx
88
+ * import { segmentParams } from './params'
89
+ * export default async function Page() {
90
+ * const { id } = await segmentParams.load() // id: number
91
+ * }
92
+ * ```
93
+ */
94
+ load(): Promise<T>;
95
+
96
+ /** Read-only codec map. */
97
+ codecs: { [K in keyof T]: Codec<T[K]> };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Internal helpers
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Wrap a Standard Schema into a Codec for route params.
106
+ *
107
+ * Unlike fromSchema for search params:
108
+ * - Parse throws on failure (no fallback to default)
109
+ * - Serialize returns string (not null)
110
+ */
111
+ function fromParamSchema<T>(fieldName: string, schema: StandardSchemaV1<T>): Codec<T> {
112
+ return {
113
+ parse(value: string | string[] | undefined): T {
114
+ // Route params are always strings (single segment) or string[] (catch-all)
115
+ const input = Array.isArray(value) ? value : value;
116
+
117
+ const result = validateSync(schema, input);
118
+ if (!result.issues) {
119
+ return result.value;
120
+ }
121
+
122
+ // For route params, parse failure means the param is invalid → throw
123
+ const messages = result.issues.map((i) => i.message).join(', ');
124
+ throw new Error(`[timber] Param '${fieldName}' coercion failed: ${messages}`);
125
+ },
126
+
127
+ serialize(value: T): string | null {
128
+ if (value === null || value === undefined) {
129
+ return null;
130
+ }
131
+ // Catch-all segments produce arrays — join with '/' for path reconstruction
132
+ if (Array.isArray(value)) {
133
+ return value.join('/');
134
+ }
135
+ return String(value);
136
+ },
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Resolve a field value to a Codec. Auto-detects Standard Schema objects.
142
+ */
143
+ function resolveField(fieldName: string, value: ParamField): Codec<unknown> {
144
+ if (isCodec(value)) {
145
+ return value;
146
+ }
147
+
148
+ if (isStandardSchema(value)) {
149
+ return fromParamSchema(fieldName, value);
150
+ }
151
+
152
+ throw new Error(
153
+ `[timber] defineSegmentParams: field '${fieldName}' is not a valid codec or Standard Schema. ` +
154
+ `Expected an object with { parse, serialize } methods, or a Standard Schema object ` +
155
+ `(Zod, Valibot, ArkType).`
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Validate that no codec's serialize returns null.
161
+ * Route params are structural — they must produce a valid path segment.
162
+ */
163
+ function validateSerialize(codecMap: Record<string, Codec<unknown>>): void {
164
+ for (const [key, codec] of Object.entries(codecMap)) {
165
+ // Test serialize with a sample parsed value to check for null
166
+ // We can't exhaustively test, but we can check that serialize(parse("test"))
167
+ // doesn't return null for a basic input.
168
+ try {
169
+ const testValue = codec.parse('test');
170
+ const serialized = codec.serialize(testValue);
171
+ if (serialized === null) {
172
+ throw new Error(
173
+ `[timber] defineSegmentParams: field '${key}' codec.serialize() returned null.\n` +
174
+ ` Route params are path segments — they cannot be omitted.\n` +
175
+ ` Ensure serialize() always returns a string.`
176
+ );
177
+ }
178
+ } catch (e) {
179
+ // parse('test') may throw for strict codecs (e.g., number-only).
180
+ // That's fine — it means the codec validates. We only care about
181
+ // serialize returning null, which we can't test without a valid value.
182
+ if (e instanceof Error && e.message.includes('returned null')) {
183
+ throw e;
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Factory
191
+ // ---------------------------------------------------------------------------
192
+
193
+ /**
194
+ * Create a ParamsDefinition from a map of codecs and/or Standard Schema objects.
195
+ *
196
+ * ```ts
197
+ * // app/products/[id]/layout.tsx
198
+ * import { defineSegmentParams } from '@timber-js/app/segment-params'
199
+ * import { z } from 'zod/v4'
200
+ *
201
+ * export const segmentParams = defineSegmentParams({
202
+ * id: z.coerce.number().int().positive(),
203
+ * })
204
+ * ```
205
+ */
206
+ export function defineSegmentParams<C extends Record<string, ParamField>>(
207
+ codecs: C
208
+ ): ParamsDefinition<{ [K in keyof C]: InferParamField<C[K]> }> {
209
+ type T = { [K in keyof C]: InferParamField<C[K]> };
210
+
211
+ const resolvedCodecs: Record<string, Codec<unknown>> = {};
212
+
213
+ for (const [key, value] of Object.entries(codecs)) {
214
+ resolvedCodecs[key] = resolveField(key, value as ParamField);
215
+ }
216
+
217
+ // Validate that serialize doesn't return null
218
+ validateSerialize(resolvedCodecs);
219
+
220
+ // ---- parse ----
221
+ function parse(raw: Record<string, string | string[]>): T {
222
+ const result: Record<string, unknown> = {};
223
+
224
+ for (const [key, codec] of Object.entries(resolvedCodecs)) {
225
+ const rawValue = raw[key];
226
+ // Route params are always present (the route matched)
227
+ result[key] = codec.parse(rawValue);
228
+ }
229
+
230
+ return result as T;
231
+ }
232
+
233
+ // ---- serialize ----
234
+ function serialize(values: T): Record<string, string> {
235
+ const result: Record<string, string> = {};
236
+
237
+ for (const [key, codec] of Object.entries(resolvedCodecs)) {
238
+ const serialized = codec.serialize(values[key as keyof T] as unknown);
239
+ if (serialized === null) {
240
+ throw new Error(
241
+ `[timber] params.serialize: field '${key}' serialized to null. ` +
242
+ `Route params must produce a valid path segment.`
243
+ );
244
+ }
245
+ result[key] = serialized;
246
+ }
247
+
248
+ return result;
249
+ }
250
+
251
+ // ---- load ----
252
+ // ALS-backed: reads segment params from the current request context.
253
+ // Server-only — throws on client.
254
+ //
255
+ // The pipeline already coerces params via coerceSegmentParams() which
256
+ // calls parse() and stores typed values in ALS via setSegmentParams().
257
+ // We return those directly instead of re-parsing, because codecs may
258
+ // not be idempotent (e.g., a codec that only accepts raw strings would
259
+ // throw if given an already-parsed value). See TIM-574.
260
+ async function load(): Promise<T> {
261
+ if (typeof window !== 'undefined') {
262
+ throw new Error(
263
+ '[timber] segmentParams.load() is server-only. ' + 'Use useSegmentParams() on the client.'
264
+ );
265
+ }
266
+ const params = await getRawSegmentParams();
267
+ // params are already coerced by the pipeline — return as-is.
268
+ return params as unknown as T;
269
+ }
270
+
271
+ const definition: ParamsDefinition<T> = {
272
+ parse,
273
+ serialize,
274
+ load,
275
+ codecs: resolvedCodecs as { [K in keyof T]: Codec<T[K]> },
276
+ };
277
+
278
+ return definition;
279
+ }
@@ -0,0 +1,28 @@
1
+ // @timber-js/app/segment-params — Typed route param coercion and search param definitions
2
+ //
3
+ // This is the primary import path for both segmentParams and searchParams.
4
+ // params.ts convention files import from here.
5
+ //
6
+ // See design/07-routing.md §"params.ts Convention File"
7
+
8
+ // --- Segment params (route path param coercion) ---
9
+ export type { ParamsDefinition, InferParamField, ParamField } from './define.js';
10
+ export { defineSegmentParams } from './define.js';
11
+
12
+ // --- Search params (re-exported from search-params for convenience) ---
13
+ // This lets params.ts import both from a single path:
14
+ // import { defineSegmentParams, defineSearchParams } from '@timber-js/app/segment-params'
15
+ export { defineSearchParams } from '../search-params/define.js';
16
+
17
+ // Codec bridges moved to @timber-js/app/codec
18
+ // Import fromSchema / fromArraySchema from '@timber-js/app/codec' instead.
19
+ export { withDefault, withUrlKey } from '../search-params/wrappers.js';
20
+ export type { Codec } from '../codec.js';
21
+ export type {
22
+ SearchParamCodec,
23
+ SearchParamsDefinition,
24
+ SetParams,
25
+ SetParamsOptions,
26
+ QueryStatesOptions,
27
+ CodecMap,
28
+ } from '../search-params/define.js';
@@ -17,6 +17,8 @@ import { DenySignal, RedirectSignal } from './primitives.js';
17
17
  import type { AccessGateProps, SlotAccessGateProps, ReactElement } from './tree-builder.js';
18
18
  import { withSpan, setSpanAttribute } from './tracing.js';
19
19
  import { isDebug } from './debug.js';
20
+ import type { DenyPageEntry } from './deny-page-resolver.js';
21
+ import { renderMatchingDenyPage, setDenyStatus } from './deny-page-resolver.js';
20
22
 
21
23
  // ─── AccessGate ─────────────────────────────────────────────────────────────
22
24
 
@@ -35,24 +37,20 @@ import { isDebug } from './debug.js';
35
37
  * gets the same data by calling the same cached functions (React.cache dedup).
36
38
  */
37
39
  export function AccessGate(props: AccessGateProps): ReactElement | Promise<ReactElement> {
38
- const { accessFn, params, searchParams, segmentName, verdict, children } = props;
40
+ const { accessFn, segmentName, verdict, denyPages, children } = props;
39
41
 
40
42
  // Fast path: replay pre-computed verdict from the pre-render pass.
41
- // This is synchronous — Suspense boundaries cannot interfere with the
42
- // status code because the signal throws before any async work.
43
43
  if (verdict !== undefined) {
44
44
  if (verdict === 'pass') {
45
45
  return children;
46
46
  }
47
- // Throw the stored DenySignal or RedirectSignal synchronously.
48
- // React catches this as a render-phase throw — the flush controller
49
- // produces the correct HTTP status code.
50
47
  throw verdict;
51
48
  }
52
49
 
53
- // Fallback: call accessFn directly (used by tree-builder.ts which
54
- // doesn't run a pre-render pass, and for backward compat).
55
- return accessGateFallback(accessFn, params, searchParams, segmentName, children);
50
+ // Primary path: call accessFn directly during render.
51
+ // If denyPages is provided, catch DenySignal and render the deny page
52
+ // in-tree no throw reaches React Flight, no second render pass.
53
+ return accessGateFallback(accessFn, segmentName, denyPages, children);
56
54
  }
57
55
 
58
56
  /**
@@ -61,28 +59,41 @@ export function AccessGate(props: AccessGateProps): ReactElement | Promise<React
61
59
  */
62
60
  async function accessGateFallback(
63
61
  accessFn: AccessGateProps['accessFn'],
64
- params: AccessGateProps['params'],
65
- searchParams: AccessGateProps['searchParams'],
66
62
  segmentName: AccessGateProps['segmentName'],
63
+ denyPages: DenyPageEntry[] | undefined,
67
64
  children: ReactElement
68
65
  ): Promise<ReactElement> {
69
- await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {
70
- try {
71
- await accessFn({ params, searchParams });
72
- await setSpanAttribute('timber.result', 'pass');
73
- } catch (error: unknown) {
74
- if (error instanceof DenySignal) {
75
- await setSpanAttribute('timber.result', 'deny');
76
- await setSpanAttribute('timber.deny_status', error.status);
77
- if (error.sourceFile) {
78
- await setSpanAttribute('timber.deny_file', error.sourceFile);
66
+ try {
67
+ await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {
68
+ try {
69
+ await accessFn();
70
+ await setSpanAttribute('timber.result', 'pass');
71
+ } catch (error: unknown) {
72
+ if (error instanceof DenySignal) {
73
+ await setSpanAttribute('timber.result', 'deny');
74
+ await setSpanAttribute('timber.deny_status', error.status);
75
+ if (error.sourceFile) {
76
+ await setSpanAttribute('timber.deny_file', error.sourceFile);
77
+ }
78
+ } else if (error instanceof RedirectSignal) {
79
+ await setSpanAttribute('timber.result', 'redirect');
79
80
  }
80
- } else if (error instanceof RedirectSignal) {
81
- await setSpanAttribute('timber.result', 'redirect');
81
+ throw error;
82
+ }
83
+ });
84
+ } catch (error: unknown) {
85
+ // Catch DenySignal and render the deny page in-tree.
86
+ // No throw reaches React Flight — clean stream, single render pass.
87
+ // RedirectSignal and other errors propagate normally.
88
+ if (error instanceof DenySignal && denyPages) {
89
+ const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);
90
+ if (denyElement) {
91
+ setDenyStatus(error.status);
92
+ return denyElement;
82
93
  }
83
- throw error;
84
94
  }
85
- });
95
+ throw error;
96
+ }
86
97
 
87
98
  return children;
88
99
  }
@@ -96,18 +107,27 @@ async function accessGateFallback(
96
107
  * The HTTP status code is unaffected — slot denial is a UI concern, not
97
108
  * a protocol concern. The parent layout and sibling slots still render.
98
109
  *
110
+ * DeniedComponent is passed instead of a pre-built element so that
111
+ * DenySignal.data can be forwarded as the dangerouslyPassData prop
112
+ * and the slot name can be passed as the slot prop. See TIM-488.
113
+ *
99
114
  * redirect() in slot access.ts is a dev-mode error — redirecting from a
100
115
  * slot doesn't make architectural sense.
101
116
  */
102
117
  export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactElement> {
103
- const { accessFn, params, searchParams, deniedFallback, defaultFallback, children } = props;
118
+ const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
104
119
 
105
120
  try {
106
- await accessFn({ params, searchParams });
121
+ await accessFn();
107
122
  } catch (error: unknown) {
108
123
  // DenySignal → graceful degradation (denied.tsx → default.tsx → null)
124
+ // Build the denied element dynamically so DenySignal.data is forwarded.
109
125
  if (error instanceof DenySignal) {
110
- return deniedFallback ?? defaultFallback ?? null;
126
+ return (
127
+ buildDeniedFallback(DeniedComponent, slotName, error.data, createElement) ??
128
+ defaultFallback ??
129
+ null
130
+ );
111
131
  }
112
132
 
113
133
  // RedirectSignal in slot access → dev-mode error.
@@ -123,7 +143,11 @@ export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactE
123
143
  );
124
144
  }
125
145
  // In production, treat as a deny — render fallback rather than crash.
126
- return deniedFallback ?? defaultFallback ?? null;
146
+ return (
147
+ buildDeniedFallback(DeniedComponent, slotName, undefined, createElement) ??
148
+ defaultFallback ??
149
+ null
150
+ );
127
151
  }
128
152
 
129
153
  // Unhandled error — re-throw so error boundaries can catch it.
@@ -141,3 +165,20 @@ export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactE
141
165
  // Access passed — render slot content.
142
166
  return children;
143
167
  }
168
+
169
+ /**
170
+ * Build the denied fallback element dynamically with DenySignal data.
171
+ * Returns null if no DeniedComponent is available.
172
+ */
173
+ function buildDeniedFallback(
174
+ DeniedComponent: SlotAccessGateProps['DeniedComponent'],
175
+ slotName: string,
176
+ data: unknown,
177
+ createElement: SlotAccessGateProps['createElement']
178
+ ): ReactElement | null {
179
+ if (!DeniedComponent) return null;
180
+ return createElement(DeniedComponent, {
181
+ slot: slotName,
182
+ dangerouslyPassData: data,
183
+ });
184
+ }
@@ -148,12 +148,23 @@ export interface ActionBuilderWithSchema<TCtx, TInput> {
148
148
  }
149
149
 
150
150
  /**
151
- * The final action function. Callable two ways:
151
+ * The final action function. Callable three ways:
152
152
  * - Direct: action(input) → Promise<ActionResult<TData>>
153
153
  * - React useActionState: action(prevState, formData) → Promise<ActionResult<TData>>
154
+ * - React <form action={fn}>: action(formData) → void (return value ignored by React)
155
+ *
156
+ * The third overload exists purely for type compatibility with React's
157
+ * `<form action>` prop, which expects `(formData: FormData) => void`.
158
+ * At runtime the function still returns Promise<ActionResult>, but React
159
+ * discards it. This lets validated actions be passed directly to forms
160
+ * without casts.
154
161
  */
155
162
  export type ActionFn<TData> = {
163
+ /** <form action={fn}> compatibility — React discards the return value. */
164
+ (formData: FormData): void;
165
+ /** Direct call: action(input) */
156
166
  (input?: unknown): Promise<ActionResult<TData>>;
167
+ /** React useActionState: action(prevState, formData) */
157
168
  (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;
158
169
  };
159
170
 
@@ -183,8 +194,9 @@ async function runActionMiddleware<TCtx>(
183
194
 
184
195
  // Re-export parseFormData for use throughout the framework
185
196
  import { parseFormData } from './form-data.js';
186
- import { formatSize } from '#/utils/format.js';
197
+ import { formatSize } from '../utils/format.js';
187
198
  import { isDebug, isDevMode } from './debug.js';
199
+ import { RedirectSignal, DenySignal } from './primitives.js';
188
200
 
189
201
  /**
190
202
  * Extract validation errors from a schema error.
@@ -295,8 +307,14 @@ export function createActionClient<TCtx = Record<string, never>>(
295
307
  // Determine input — either FormData (from useActionState) or direct arg
296
308
  let rawInput: unknown;
297
309
  if (args.length === 2 && args[1] instanceof FormData) {
298
- // Called as (prevState, formData) by React useActionState
310
+ // Called as (prevState, formData) by React useActionState (with-JS path)
299
311
  rawInput = schema ? parseFormData(args[1]) : args[1];
312
+ } else if (args.length === 1 && args[0] instanceof FormData) {
313
+ // No-JS path: React's decodeAction binds FormData as the sole argument.
314
+ // The form POSTs without JavaScript, decodeAction resolves the server
315
+ // reference and binds the FormData, then executeAction calls fn() with
316
+ // no additional args — so the bound FormData arrives as args[0].
317
+ rawInput = schema ? parseFormData(args[0]) : args[0];
300
318
  } else {
301
319
  // Direct call: action(input)
302
320
  rawInput = args[0];
@@ -360,6 +378,13 @@ export function createActionClient<TCtx = Record<string, never>>(
360
378
  const data = await fn({ ctx, input });
361
379
  return { data };
362
380
  } catch (error) {
381
+ // Re-throw redirect/deny signals — these are control flow, not errors.
382
+ // They must propagate to executeAction() which converts them to proper
383
+ // HTTP responses (302 redirect, 4xx deny). Catching them here would
384
+ // wrap them as INTERNAL_ERROR and break redirect()/redirectExternal()/deny().
385
+ if (error instanceof RedirectSignal || error instanceof DenySignal) {
386
+ throw error;
387
+ }
363
388
  return handleActionError(error);
364
389
  }
365
390
  }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Server action bound args encryption utilities.
3
+ *
4
+ * Provides key management for the RSC plugin's built-in bound args encryption.
5
+ * The RSC plugin (@vitejs/plugin-rsc) handles the actual encrypt/decrypt via
6
+ * AES-256-GCM — this module handles:
7
+ *
8
+ * 1. Key sourcing: auto-generated at build time (embedded in bundle), overridable
9
+ * via env var for cross-build key sharing (rolling/blue-green deployments)
10
+ * 2. Build-time key expression generation for the RSC plugin's `defineEncryptionKey`
11
+ *
12
+ * Encryption is always on in production. In dev mode, it's on by default
13
+ * (matching the RSC plugin's behavior) but can be disabled for debugging.
14
+ *
15
+ * ## Known Security Considerations
16
+ *
17
+ * 1. **defineEncryptionKey is a raw JS expression.** The RSC plugin inlines it
18
+ * verbatim into generated code. We only emit the hardcoded string
19
+ * `process.env.TIMBER_ACTIONS_ENCRYPTION_KEY` — never user-controlled input.
20
+ * If this function is ever extended to accept configurable env var names,
21
+ * the expression MUST be validated against a safe pattern.
22
+ *
23
+ * 2. **Key material lives in GC-visible JS strings.** `atob()` decodes the key
24
+ * into a regular JavaScript string on the V8 heap. JavaScript has no
25
+ * `SecureString` or memory-zeroing primitive — this is an inherent platform
26
+ * limitation. Acceptable for web server use; would need review for FIPS.
27
+ *
28
+ * 3. **TIMBER_ACTIONS_ENCRYPTION_KEY must be set at both build time and runtime.**
29
+ * At build time, we validate the key format and emit a runtime expression.
30
+ * If the env var is present at build time but missing at runtime, the server
31
+ * will crash on first action invocation with an opaque `atob(undefined)` error.
32
+ * If the env var is present at runtime but was absent at build time, the RSC
33
+ * plugin will have generated its own key and the env var is silently ignored.
34
+ *
35
+ * See design/08-forms-and-actions.md §"Security"
36
+ * See design/13-security.md
37
+ */
38
+
39
+ // ─── Types ────────────────────────────────────────────────────────────────
40
+
41
+ /** User-facing configuration for action bound args encryption. */
42
+ export interface ActionEncryptionConfig {
43
+ /**
44
+ * Disable encryption in dev mode for easier debugging.
45
+ * Has no effect in production — encryption is always enabled.
46
+ * Default: false (encryption is on in dev too).
47
+ */
48
+ disableInDev?: boolean;
49
+ }
50
+
51
+ // ─── Key Resolution ───────────────────────────────────────────────────────
52
+
53
+ /**
54
+ * Regex for safe `defineEncryptionKey` expressions.
55
+ *
56
+ * The RSC plugin inlines this expression verbatim into generated JavaScript.
57
+ * We restrict it to `process.env.<UPPER_SNAKE_CASE>` to prevent code injection.
58
+ * See "Known Security Considerations" at the top of this file.
59
+ */
60
+ const SAFE_KEY_EXPR = /^process\.env\.[A-Z_][A-Z0-9_]*$/;
61
+
62
+ /**
63
+ * Build the `defineEncryptionKey` expression for the RSC plugin.
64
+ *
65
+ * The RSC plugin accepts a JavaScript expression string that will be
66
+ * inlined into the encryption runtime module. At runtime, this expression
67
+ * must evaluate to the base64-encoded encryption key.
68
+ *
69
+ * Priority:
70
+ * 1. `TIMBER_ACTIONS_ENCRYPTION_KEY` env var (for cross-build key sharing
71
+ * in rolling/blue-green deployments)
72
+ * 2. Auto-generated at build time (RSC plugin default — embedded in bundle,
73
+ * consistent across all instances of the same build)
74
+ *
75
+ * For env var keys, we generate a runtime expression that reads the env var.
76
+ * For auto-generated keys, we return undefined and let the RSC plugin handle it.
77
+ */
78
+ export function resolveEncryptionKeyExpression(): string | undefined {
79
+ // Check for env var override — used for cross-build key sharing where
80
+ // multiple builds must agree on the same encryption key.
81
+ const envKey = process.env.TIMBER_ACTIONS_ENCRYPTION_KEY;
82
+ if (envKey) {
83
+ // Validate the key format (must be base64-encoded 32-byte key)
84
+ validateKeyFormat(envKey);
85
+
86
+ // Return a runtime expression that reads the env var at startup.
87
+ // This ensures the key is read at runtime, not embedded in the build.
88
+ const expr = 'process.env.TIMBER_ACTIONS_ENCRYPTION_KEY';
89
+
90
+ // Defense-in-depth: validate the expression matches our safe pattern.
91
+ // This is redundant today (hardcoded string), but protects against
92
+ // future refactors that might make the expression configurable.
93
+ if (!SAFE_KEY_EXPR.test(expr)) {
94
+ throw new Error(`Unsafe encryption key expression: ${expr}`);
95
+ }
96
+
97
+ return expr;
98
+ }
99
+
100
+ // No override — let the RSC plugin auto-generate a per-build key
101
+ return undefined;
102
+ }
103
+
104
+ /**
105
+ * Determine whether action encryption should be enabled.
106
+ *
107
+ * Encryption is always enabled in production. In dev mode, it's enabled
108
+ * by default but can be disabled via config for debugging.
109
+ */
110
+ export function shouldEnableEncryption(isDev: boolean, config?: ActionEncryptionConfig): boolean {
111
+ if (!isDev) return true; // Always on in production
112
+ if (config?.disableInDev) return false; // Opt-out in dev
113
+ return true; // On by default in dev too
114
+ }
115
+
116
+ // ─── Key Validation ───────────────────────────────────────────────────────
117
+
118
+ /**
119
+ * Validate that a key string is a valid base64-encoded 256-bit key.
120
+ * Throws a descriptive error if the key is malformed.
121
+ */
122
+ export function validateKeyFormat(key: string): void {
123
+ // Decode base64 and check length (32 bytes = 256 bits)
124
+ try {
125
+ const decoded = atob(key);
126
+ const bytes = decoded.length;
127
+ if (bytes !== 32) {
128
+ throw new Error(
129
+ `TIMBER_ACTIONS_ENCRYPTION_KEY must be a base64-encoded 256-bit (32-byte) key. ` +
130
+ `Got ${bytes} bytes. Generate one with: ` +
131
+ `node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"`
132
+ );
133
+ }
134
+ } catch (error) {
135
+ if (error instanceof Error && error.message.includes('TIMBER_ACTIONS_ENCRYPTION_KEY')) {
136
+ throw error;
137
+ }
138
+ throw new Error(
139
+ `TIMBER_ACTIONS_ENCRYPTION_KEY is not valid base64. ` +
140
+ `Generate a key with: ` +
141
+ `node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"`
142
+ );
143
+ }
144
+ }