@timber-js/app 0.2.0-alpha.98 → 0.2.0-alpha.99

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 (322) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
  3. package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
  4. package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
  5. package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
  6. package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
  7. package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
  8. package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
  9. package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
  10. package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
  11. package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
  12. package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
  13. package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
  14. package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
  15. package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
  16. package/dist/_chunks/logger-0m8MsKdc.js +291 -0
  17. package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
  18. package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
  19. package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
  20. package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
  21. package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
  22. package/dist/_chunks/registry-I2ss-lvy.js +20 -0
  23. package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
  24. package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
  25. package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
  26. package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
  27. package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
  28. package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
  29. package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
  30. package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
  31. package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
  32. package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
  33. package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
  34. package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
  35. package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
  36. package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
  37. package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
  38. package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
  39. package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
  40. package/dist/_chunks/{walkers-VOXgavMF.js → walkers-Tg0Alwcg.js} +6 -3
  41. package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
  42. package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
  43. package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
  44. package/dist/adapters/build-output-helper.d.ts +28 -0
  45. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  46. package/dist/adapters/cloudflare.d.ts.map +1 -1
  47. package/dist/adapters/cloudflare.js +8 -28
  48. package/dist/adapters/cloudflare.js.map +1 -1
  49. package/dist/adapters/nitro.d.ts.map +1 -1
  50. package/dist/adapters/nitro.js +8 -26
  51. package/dist/adapters/nitro.js.map +1 -1
  52. package/dist/adapters/shared.d.ts +16 -0
  53. package/dist/adapters/shared.d.ts.map +1 -0
  54. package/dist/cache/index.js +9 -2
  55. package/dist/cache/index.js.map +1 -1
  56. package/dist/cache/timber-cache.d.ts.map +1 -1
  57. package/dist/client/error-boundary.js +2 -1
  58. package/dist/client/error-boundary.js.map +1 -1
  59. package/dist/client/form.d.ts +10 -24
  60. package/dist/client/form.d.ts.map +1 -1
  61. package/dist/client/index.d.ts +1 -5
  62. package/dist/client/index.d.ts.map +1 -1
  63. package/dist/client/index.js +40 -90
  64. package/dist/client/index.js.map +1 -1
  65. package/dist/client/internal.d.ts +2 -1
  66. package/dist/client/internal.d.ts.map +1 -1
  67. package/dist/client/internal.js +81 -7
  68. package/dist/client/internal.js.map +1 -1
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/state.d.ts +1 -1
  71. package/dist/client/use-cookie.d.ts +8 -0
  72. package/dist/client/use-cookie.d.ts.map +1 -1
  73. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
  74. package/dist/client/use-segment-params.d.ts.map +1 -0
  75. package/dist/codec.d.ts +1 -1
  76. package/dist/codec.d.ts.map +1 -1
  77. package/dist/codec.js +2 -2
  78. package/dist/config-types.d.ts +28 -0
  79. package/dist/config-types.d.ts.map +1 -1
  80. package/dist/cookies/define-cookie.d.ts +87 -35
  81. package/dist/cookies/define-cookie.d.ts.map +1 -1
  82. package/dist/cookies/index.d.ts +2 -1
  83. package/dist/cookies/index.d.ts.map +1 -1
  84. package/dist/cookies/index.js +48 -2
  85. package/dist/cookies/index.js.map +1 -0
  86. package/dist/cookies/json-cookie.d.ts +64 -0
  87. package/dist/cookies/json-cookie.d.ts.map +1 -0
  88. package/dist/cookies/validation.d.ts +46 -0
  89. package/dist/cookies/validation.d.ts.map +1 -0
  90. package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +1 -1
  91. package/dist/dev-tools/404-page.d.ts.map +1 -0
  92. package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
  93. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  94. package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
  95. package/dist/dev-tools/error-page.d.ts.map +1 -0
  96. package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +1 -1
  97. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  98. package/dist/dev-tools/index.d.ts +31 -0
  99. package/dist/dev-tools/index.d.ts.map +1 -0
  100. package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
  101. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  102. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  103. package/dist/dev-tools/logger.d.ts.map +1 -0
  104. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
  105. package/dist/dev-tools/logs.d.ts.map +1 -0
  106. package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
  107. package/dist/dev-tools/overlay.d.ts.map +1 -0
  108. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  109. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  110. package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
  111. package/dist/dev-tools/terminal.d.ts.map +1 -0
  112. package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
  113. package/dist/dev-tools/warnings.d.ts.map +1 -0
  114. package/dist/index.d.ts +1 -0
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +97 -72
  117. package/dist/index.js.map +1 -1
  118. package/dist/plugin-context.d.ts +1 -1
  119. package/dist/plugin-context.d.ts.map +1 -1
  120. package/dist/plugins/adapter-build.d.ts.map +1 -1
  121. package/dist/routing/convention-lint.d.ts.map +1 -1
  122. package/dist/routing/index.js +1 -1
  123. package/dist/routing/scanner.d.ts.map +1 -1
  124. package/dist/routing/status-file-lint.d.ts.map +1 -1
  125. package/dist/search-params/define.d.ts +25 -7
  126. package/dist/search-params/define.d.ts.map +1 -1
  127. package/dist/search-params/index.js +5 -3
  128. package/dist/search-params/index.js.map +1 -1
  129. package/dist/search-params/wrappers.d.ts +2 -2
  130. package/dist/search-params/wrappers.d.ts.map +1 -1
  131. package/dist/segment-params/define.d.ts +23 -6
  132. package/dist/segment-params/define.d.ts.map +1 -1
  133. package/dist/segment-params/index.js +1 -1
  134. package/dist/server/access-gate.d.ts +4 -3
  135. package/dist/server/access-gate.d.ts.map +1 -1
  136. package/dist/server/action-handler.d.ts +15 -6
  137. package/dist/server/action-handler.d.ts.map +1 -1
  138. package/dist/server/als-registry.d.ts +5 -5
  139. package/dist/server/als-registry.d.ts.map +1 -1
  140. package/dist/server/asset-headers.d.ts +1 -15
  141. package/dist/server/asset-headers.d.ts.map +1 -1
  142. package/dist/server/cookie-context.d.ts +170 -0
  143. package/dist/server/cookie-context.d.ts.map +1 -0
  144. package/dist/server/cookie-parsing.d.ts +51 -0
  145. package/dist/server/cookie-parsing.d.ts.map +1 -0
  146. package/dist/server/deny-boundary.d.ts +90 -0
  147. package/dist/server/deny-boundary.d.ts.map +1 -0
  148. package/dist/server/deny-renderer.d.ts.map +1 -1
  149. package/dist/server/early-hints-sender.d.ts.map +1 -1
  150. package/dist/server/index.d.ts +5 -4
  151. package/dist/server/index.d.ts.map +1 -1
  152. package/dist/server/index.js +4 -149
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/internal.d.ts +6 -4
  155. package/dist/server/internal.d.ts.map +1 -1
  156. package/dist/server/internal.js +261 -408
  157. package/dist/server/internal.js.map +1 -1
  158. package/dist/server/logger.d.ts +14 -0
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/middleware-runner.d.ts +17 -0
  161. package/dist/server/middleware-runner.d.ts.map +1 -1
  162. package/dist/server/param-coercion.d.ts +26 -0
  163. package/dist/server/param-coercion.d.ts.map +1 -0
  164. package/dist/server/pipeline-helpers.d.ts +14 -7
  165. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  166. package/dist/server/pipeline-outcome.d.ts +49 -0
  167. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  168. package/dist/server/pipeline-phases.d.ts +4 -49
  169. package/dist/server/pipeline-phases.d.ts.map +1 -1
  170. package/dist/server/pipeline.d.ts +0 -2
  171. package/dist/server/pipeline.d.ts.map +1 -1
  172. package/dist/server/request-context.d.ts +22 -159
  173. package/dist/server/request-context.d.ts.map +1 -1
  174. package/dist/server/route-element-builder.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  176. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  177. package/dist/server/rsc-entry/helpers.d.ts +1 -1
  178. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/render-route.d.ts +50 -0
  181. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  182. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +59 -14
  183. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
  184. package/dist/server/state-tree-diff.d.ts.map +1 -1
  185. package/dist/server/tracing.d.ts +1 -1
  186. package/dist/server/tracing.d.ts.map +1 -1
  187. package/dist/server/tree-builder.d.ts +45 -16
  188. package/dist/server/tree-builder.d.ts.map +1 -1
  189. package/dist/server/types.d.ts +48 -0
  190. package/dist/server/types.d.ts.map +1 -1
  191. package/dist/server/utils/escape-html.d.ts +14 -0
  192. package/dist/server/utils/escape-html.d.ts.map +1 -0
  193. package/dist/shims/headers.d.ts +2 -2
  194. package/dist/shims/headers.d.ts.map +1 -1
  195. package/dist/shims/navigation-client.d.ts +3 -1
  196. package/dist/shims/navigation-client.d.ts.map +1 -1
  197. package/dist/shims/navigation.d.ts +9 -4
  198. package/dist/shims/navigation.d.ts.map +1 -1
  199. package/package.json +6 -7
  200. package/src/adapters/build-output-helper.ts +77 -0
  201. package/src/adapters/cloudflare.ts +10 -50
  202. package/src/adapters/nitro.ts +11 -45
  203. package/src/adapters/shared.ts +40 -0
  204. package/src/cache/timber-cache.ts +3 -2
  205. package/src/cli.ts +0 -0
  206. package/src/client/form.tsx +17 -25
  207. package/src/client/index.ts +16 -9
  208. package/src/client/internal.ts +3 -2
  209. package/src/client/router.ts +1 -1
  210. package/src/client/rsc-fetch.ts +15 -0
  211. package/src/client/state.ts +2 -2
  212. package/src/client/use-cookie.ts +29 -0
  213. package/src/codec.ts +3 -7
  214. package/src/config-types.ts +28 -0
  215. package/src/cookies/define-cookie.ts +271 -78
  216. package/src/cookies/index.ts +11 -8
  217. package/src/cookies/json-cookie.ts +105 -0
  218. package/src/cookies/validation.ts +134 -0
  219. package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +2 -7
  220. package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
  221. package/src/dev-tools/index.ts +90 -0
  222. package/src/dev-tools/instrumentation.ts +176 -0
  223. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
  224. package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
  225. package/src/dev-tools/stack-classifier.ts +75 -0
  226. package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
  227. package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
  228. package/src/index.ts +11 -3
  229. package/src/plugin-context.ts +1 -1
  230. package/src/plugins/adapter-build.ts +3 -1
  231. package/src/plugins/dev-server.ts +3 -3
  232. package/src/plugins/shims.ts +1 -1
  233. package/src/plugins/static-build.ts +1 -1
  234. package/src/routing/convention-lint.ts +5 -4
  235. package/src/routing/scanner.ts +5 -2
  236. package/src/routing/status-file-lint.ts +4 -2
  237. package/src/search-params/define.ts +71 -15
  238. package/src/search-params/wrappers.ts +9 -2
  239. package/src/segment-params/define.ts +66 -13
  240. package/src/server/access-gate.tsx +9 -8
  241. package/src/server/action-handler.ts +28 -38
  242. package/src/server/als-registry.ts +5 -5
  243. package/src/server/asset-headers.ts +8 -34
  244. package/src/server/cookie-context.ts +468 -0
  245. package/src/server/cookie-parsing.ts +135 -0
  246. package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
  247. package/src/server/deny-renderer.ts +2 -7
  248. package/src/server/early-hints-sender.ts +3 -2
  249. package/src/server/fallback-error.ts +1 -1
  250. package/src/server/index.ts +13 -14
  251. package/src/server/internal.ts +10 -3
  252. package/src/server/logger.ts +23 -0
  253. package/src/server/middleware-runner.ts +44 -0
  254. package/src/server/param-coercion.ts +76 -0
  255. package/src/server/pipeline-helpers.ts +37 -13
  256. package/src/server/pipeline-outcome.ts +167 -0
  257. package/src/server/pipeline-phases.ts +27 -209
  258. package/src/server/pipeline.ts +2 -9
  259. package/src/server/request-context.ts +46 -451
  260. package/src/server/route-element-builder.ts +7 -3
  261. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  262. package/src/server/rsc-entry/error-renderer.ts +1 -1
  263. package/src/server/rsc-entry/helpers.ts +2 -7
  264. package/src/server/rsc-entry/index.ts +34 -273
  265. package/src/server/rsc-entry/render-route.ts +304 -0
  266. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  267. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  268. package/src/server/rsc-entry/wrap-action-dispatch.ts +316 -23
  269. package/src/server/ssr-entry.ts +1 -1
  270. package/src/server/state-tree-diff.ts +4 -1
  271. package/src/server/tracing.ts +3 -3
  272. package/src/server/tree-builder.ts +128 -52
  273. package/src/server/types.ts +52 -0
  274. package/src/server/utils/escape-html.ts +20 -0
  275. package/src/shims/headers.ts +3 -3
  276. package/src/shims/navigation-client.ts +4 -3
  277. package/src/shims/navigation.ts +9 -7
  278. package/dist/_chunks/actions-DLnUaR65.js +0 -421
  279. package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
  280. package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
  281. package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
  282. package/dist/_chunks/debug-ECi_61pb.js +0 -108
  283. package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
  284. package/dist/_chunks/define-C77ScO0m.js.map +0 -1
  285. package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
  286. package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
  287. package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
  288. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
  289. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
  290. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
  291. package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
  292. package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
  293. package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
  294. package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
  295. package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
  296. package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
  297. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
  298. package/dist/_chunks/walkers-VOXgavMF.js.map +0 -1
  299. package/dist/client/use-params.d.ts.map +0 -1
  300. package/dist/plugins/dev-404-page.d.ts.map +0 -1
  301. package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
  302. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  303. package/dist/plugins/dev-error-page.d.ts.map +0 -1
  304. package/dist/plugins/dev-logs.d.ts.map +0 -1
  305. package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
  306. package/dist/server/deny-page-resolver.d.ts +0 -52
  307. package/dist/server/deny-page-resolver.d.ts.map +0 -1
  308. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  309. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  310. package/dist/server/dev-holding-server.d.ts.map +0 -1
  311. package/dist/server/dev-logger.d.ts.map +0 -1
  312. package/dist/server/dev-span-processor.d.ts.map +0 -1
  313. package/dist/server/dev-warnings.d.ts.map +0 -1
  314. package/dist/server/page-deny-boundary.d.ts +0 -31
  315. package/dist/server/page-deny-boundary.d.ts.map +0 -1
  316. package/src/server/dev-fetch-instrumentation.ts +0 -96
  317. package/src/server/dev-span-processor.ts +0 -78
  318. package/src/server/page-deny-boundary.tsx +0 -56
  319. /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
  320. /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
  321. /package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +0 -0
  322. /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
@@ -2,31 +2,27 @@
2
2
  * defineCookie — typed cookie definitions.
3
3
  *
4
4
  * Bundles name + codec + options into a reusable CookieDefinition<T>
5
- * with async .getCookie(), .setCookie(), .deleteCookie() server methods
6
- * and a sync .useCookie() client hook.
5
+ * with sync .get(), .set(), .delete() isomorphic methods (server + client)
6
+ * and a .useCookie() client hook.
7
7
  *
8
- * Server methods are async to future-proof the API for v2 features
9
- * (signed cookies via crypto.subtle, encrypted cookies, external stores).
8
+ * Uses the registration pattern: server entry registers serverCookieImpl,
9
+ * client entry registers clientCookieImpl. This avoids top-level value
10
+ * imports from either environment and makes defineCookie isomorphic
11
+ * without `typeof window` checks.
10
12
  *
11
- * Reuses the SearchParamCodec protocol via fromSchema() bridge.
12
- * Validation on read returns the codec default (never throws).
13
- *
14
- * IMPORTANT: This module must NOT have top-level value imports from either
15
- * server or client modules. Server methods lazy-import request-context;
16
- * useCookie() lazy-imports use-cookie. This ensures:
17
- * - Client bundles don't pull in ALS/server code
18
- * - RSC bundles don't pull in useSyncExternalStore/client code
19
- * - Tree-shaking is not required for correctness
13
+ * Standard Schema objects (Zod, Valibot, ArkType) are auto-detected in
14
+ * the codec option no explicit fromSchema() wrapper needed.
20
15
  *
21
16
  * See design/29-cookies.md §"Typed Cookies with Schema Validation"
22
17
  */
23
18
 
24
- import type { CookieOptions } from '../server/request-context.js';
19
+ import type { CookieOptions } from '../server/cookie-context.js';
25
20
  import type { ClientCookieOptions } from '../client/use-cookie.js';
26
21
 
27
22
  // ─── Types ────────────────────────────────────────────────────────────────
28
23
 
29
24
  import type { Codec } from '../codec.js';
25
+ import type { StandardSchemaV1 } from '../schema-bridge.js';
30
26
 
31
27
  /**
32
28
  * A codec that converts between string cookie values and typed values.
@@ -35,46 +31,110 @@ import type { Codec } from '../codec.js';
35
31
  export type CookieCodec<T> = Codec<T>;
36
32
 
37
33
  /** Options for defineCookie: codec + CookieOptions merged. */
38
- export interface DefineCookieOptions<T> extends CookieOptions {
39
- /** Codec for parsing/serializing the cookie value. */
40
- codec: CookieCodec<T>;
34
+ export interface DefineCookieOptions<T, HttpOnly extends boolean = boolean> extends CookieOptions {
35
+ /**
36
+ * Codec for parsing/serializing the cookie value.
37
+ * Accepts a Codec<T> or a Standard Schema object (Zod, Valibot, ArkType)
38
+ * which is auto-wrapped via fromSchema.
39
+ */
40
+ codec: CookieCodec<T> | StandardSchemaV1<T>;
41
+ /**
42
+ * Prevent client-side JS access. Default: true.
43
+ * When true (or omitted), client methods (useCookie, get/set/delete on
44
+ * client) are omitted from the return type and throw at runtime.
45
+ */
46
+ httpOnly?: HttpOnly;
41
47
  }
42
48
 
43
- /** A fully typed cookie definition with server and client methods. */
44
- export interface CookieDefinition<T> {
45
- /** The cookie name. */
49
+ /**
50
+ * Server-only cookie definition. Returned when httpOnly is true or omitted.
51
+ * Client methods are absent from the type — accessing them is a TS error.
52
+ */
53
+ export interface ServerOnlyCookieDefinition<T> {
46
54
  readonly name: string;
47
- /** The resolved cookie options (without codec). */
48
55
  readonly options: CookieOptions;
49
- /** The codec used for parsing/serializing. */
50
56
  readonly codec: CookieCodec<T>;
51
57
 
52
- /** Server: read the typed value from the current request. */
53
- getCookie(): Promise<T>;
54
- /** Server: set the typed value on the response. */
55
- setCookie(value: T): Promise<void>;
56
- /** Server: delete the cookie. */
57
- deleteCookie(): Promise<void>;
58
+ /** Read the typed value. Sync, isomorphic (server + client). */
59
+ get(): T;
60
+ /** Set the typed value. Sync, isomorphic (server + client). */
61
+ set(value: T): void;
62
+ /** Delete the cookie. Sync, isomorphic (server + client). */
63
+ delete(): void;
64
+ }
58
65
 
66
+ /**
67
+ * Full cookie definition with client methods. Returned when httpOnly: false.
68
+ */
69
+ export interface CookieDefinition<T> extends ServerOnlyCookieDefinition<T> {
59
70
  /** Client: React hook for reading/writing this cookie. Returns [value, setter, deleter]. */
60
71
  useCookie(): [T, (value: T) => void, () => void];
61
72
  }
62
73
 
63
- // ─── Lazy Module References ───────────────────────────────────────────────
74
+ // ─── Registration Pattern ─────────────────────────────────────────────────
64
75
  //
65
- // These are resolved on first use, not at module load time. This prevents
66
- // the server module graph from pulling in client code and vice versa.
67
- // The dynamic import() in server methods is natural (they're async).
68
- // For useCookie() (sync), we cache the module reference after first load.
76
+ // Server and client entries register their implementations at module load
77
+ // time. This avoids dynamic imports (which lose ALS context) and typeof
78
+ // window checks (which are fragile).
79
+
80
+ /** Server-side cookie impl: reads from ALS-backed cookie jar. */
81
+ export interface ServerCookieImpl {
82
+ getCookieJar(): {
83
+ get(name: string): string | undefined;
84
+ set(name: string, value: string, options?: CookieOptions): void;
85
+ delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;
86
+ };
87
+ }
88
+
89
+ /** Client-side cookie impl: reads/writes document.cookie via useCookie hook. */
90
+ export interface ClientCookieImpl {
91
+ useCookie(
92
+ name: string,
93
+ options?: ClientCookieOptions
94
+ ): [string | undefined, (value: string, options?: ClientCookieOptions) => void, () => void];
95
+ getCookieValue(name: string): string | undefined;
96
+ setCookieValue(name: string, value: string, options?: ClientCookieOptions): void;
97
+ deleteCookieValue(name: string, options?: ClientCookieOptions): void;
98
+ }
99
+
100
+ let _serverImpl: ServerCookieImpl | undefined;
101
+ let _clientImpl: ClientCookieImpl | undefined;
102
+ let _fromSchemaFn: ((schema: StandardSchemaV1<unknown>) => CookieCodec<unknown>) | undefined;
103
+
104
+ /**
105
+ * Register the server-side cookie implementation.
106
+ * Called by server entry at module load time.
107
+ * @internal
108
+ */
109
+ export function _registerServerCookieImpl(impl: ServerCookieImpl): void {
110
+ _serverImpl = impl;
111
+ }
69
112
 
113
+ /**
114
+ * Register the client-side cookie implementation.
115
+ * Called by client entry at module load time.
116
+ * @internal
117
+ */
118
+ export function _registerClientCookieImpl(impl: ClientCookieImpl): void {
119
+ _clientImpl = impl;
120
+ }
121
+
122
+ /**
123
+ * Register the fromSchema bridge function.
124
+ * Called by server/client entry at module load time.
125
+ * @internal
126
+ */
127
+ export function _registerFromSchema(
128
+ fn: (schema: StandardSchemaV1<unknown>) => CookieCodec<unknown>
129
+ ): void {
130
+ _fromSchemaFn = fn;
131
+ }
132
+
133
+ // Legacy registration for useCookie module — still used by client/index.ts
70
134
  let _useCookieModule: typeof import('../client/use-cookie.js') | undefined;
71
135
 
72
136
  function getUseCookieModule(): typeof import('../client/use-cookie.js') {
73
137
  if (!_useCookieModule) {
74
- // In the client/SSR environment, this module is already in the module
75
- // graph (imported by the client entry). The throw is a safeguard —
76
- // if useCookie() is somehow called before the module is available,
77
- // the developer gets a clear error instead of a silent failure.
78
138
  throw new Error(
79
139
  '[timber] defineCookie().useCookie() requires @timber-js/app/client to be loaded. ' +
80
140
  'This hook can only be used in client components.'
@@ -91,6 +151,68 @@ function getUseCookieModule(): typeof import('../client/use-cookie.js') {
91
151
  */
92
152
  export function _registerUseCookieModule(mod: typeof import('../client/use-cookie.js')): void {
93
153
  _useCookieModule = mod;
154
+ // Also register the client impl from the module
155
+ _clientImpl = {
156
+ useCookie: mod.useCookie,
157
+ getCookieValue: (name: string) => {
158
+ if (typeof document === 'undefined') return undefined;
159
+ const match = document.cookie.match(
160
+ new RegExp('(?:^|;\\s*)' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*=\\s*([^;]*)')
161
+ );
162
+ return match ? decodeURIComponent(match[1]) : undefined;
163
+ },
164
+ setCookieValue: (name: string, value: string, options?: ClientCookieOptions) => {
165
+ const parts: string[] = [`${name}=${encodeURIComponent(value)}`];
166
+ const path = options?.path ?? '/';
167
+ parts.push(`Path=${path}`);
168
+ if (options?.domain) parts.push(`Domain=${options.domain}`);
169
+ if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
170
+ if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
171
+ const sameSite = options?.sameSite ?? 'lax';
172
+ parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
173
+ if (options?.secure) parts.push('Secure');
174
+ document.cookie = parts.join('; ');
175
+ // Notify useCookie subscribers so mounted hooks re-render
176
+ mod.notifyCookieChange(name);
177
+ },
178
+ deleteCookieValue: (name: string, options?: ClientCookieOptions) => {
179
+ const path = options?.path ?? '/';
180
+ const domain = options?.domain;
181
+ let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;
182
+ if (domain) cookieStr += `; Domain=${domain}`;
183
+ document.cookie = cookieStr;
184
+ mod.notifyCookieChange(name);
185
+ },
186
+ };
187
+ }
188
+
189
+ // ─── Standard Schema Auto-Detection ───────────────────────────────────────
190
+
191
+ function resolveCodec<T>(codecOrSchema: CookieCodec<T> | StandardSchemaV1<T>): CookieCodec<T> {
192
+ // If it has parse + serialize, it's already a Codec
193
+ if (
194
+ typeof (codecOrSchema as Codec<T>).parse === 'function' &&
195
+ typeof (codecOrSchema as Codec<T>).serialize === 'function'
196
+ ) {
197
+ return codecOrSchema as CookieCodec<T>;
198
+ }
199
+
200
+ // Auto-detect Standard Schema
201
+ if (typeof codecOrSchema === 'object' && codecOrSchema !== null && '~standard' in codecOrSchema) {
202
+ if (!_fromSchemaFn) {
203
+ throw new Error(
204
+ '[timber] defineCookie: Standard Schema auto-detection requires @timber-js/app/server or ' +
205
+ '@timber-js/app/client to be loaded. Pass an explicit Codec<T> with parse/serialize methods, ' +
206
+ 'or ensure the framework entry module is imported.'
207
+ );
208
+ }
209
+ return _fromSchemaFn(codecOrSchema as StandardSchemaV1<unknown>) as CookieCodec<T>;
210
+ }
211
+
212
+ throw new Error(
213
+ '[timber] defineCookie: codec must be a Codec<T> (with parse/serialize methods) ' +
214
+ 'or a Standard Schema object (Zod, Valibot, ArkType).'
215
+ );
94
216
  }
95
217
 
96
218
  // ─── Factory ──────────────────────────────────────────────────────────────
@@ -100,78 +222,147 @@ export function _registerUseCookieModule(mod: typeof import('../client/use-cooki
100
222
  *
101
223
  * ```ts
102
224
  * import { defineCookie } from '@timber-js/app/cookies';
103
- * import { fromSchema } from '@timber-js/app/codec';
104
225
  * import { z } from 'zod/v4';
105
226
  *
227
+ * // httpOnly: false — client methods available
106
228
  * export const themeCookie = defineCookie('theme', {
107
- * codec: fromSchema(z.enum(['light', 'dark', 'system']).default('system')),
229
+ * codec: z.enum(['light', 'dark', 'system']).default('system'),
108
230
  * httpOnly: false,
109
231
  * maxAge: 60 * 60 * 24 * 365,
110
232
  * });
111
233
  *
112
- * // Server
113
- * const theme = await themeCookie.getCookie();
114
- * await themeCookie.setCookie('dark');
234
+ * // Server or client
235
+ * const theme = themeCookie.get();
236
+ * themeCookie.set('dark');
115
237
  *
116
- * // Client
238
+ * // Client hook
117
239
  * const [theme, setTheme] = themeCookie.useCookie();
240
+ *
241
+ * // httpOnly: true (default) — server-only, no client methods
242
+ * export const sessionCookie = defineCookie('session', {
243
+ * codec: z.string(),
244
+ * });
245
+ * sessionCookie.get(); // works on server
246
+ * sessionCookie.useCookie(); // TS error — httpOnly cookie
118
247
  * ```
119
248
  */
120
- export function defineCookie<T>(
249
+ export function defineCookie<T, HttpOnly extends boolean = true>(
121
250
  name: string,
122
- options: DefineCookieOptions<T>
123
- ): CookieDefinition<T> {
124
- const { codec, ...cookieOpts } = options;
251
+ options: DefineCookieOptions<T, HttpOnly>
252
+ ): HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T> {
253
+ const { codec: codecOrSchema, ...cookieOpts } = options;
254
+ const codec = resolveCodec(codecOrSchema);
125
255
  const resolvedOptions: CookieOptions = { ...cookieOpts };
256
+ const isHttpOnly = options.httpOnly !== false; // default true
257
+
258
+ function getClientOpts(): ClientCookieOptions {
259
+ return {
260
+ path: resolvedOptions.path,
261
+ domain: resolvedOptions.domain,
262
+ maxAge: resolvedOptions.maxAge,
263
+ expires: resolvedOptions.expires,
264
+ sameSite: resolvedOptions.sameSite,
265
+ secure: resolvedOptions.secure,
266
+ };
267
+ }
268
+
269
+ function assertNotHttpOnlyClient(method: string): void {
270
+ if (isHttpOnly && !_serverImpl) {
271
+ throw new Error(
272
+ `[timber] defineCookie('${name}').${method}() cannot be used with httpOnly cookies — ` +
273
+ `the browser cannot access them. Set httpOnly: false to enable client access.`
274
+ );
275
+ }
276
+ }
126
277
 
127
- return {
278
+ const base: ServerOnlyCookieDefinition<T> = {
128
279
  name,
129
280
  options: resolvedOptions,
130
281
  codec,
131
282
 
132
- async getCookie(): Promise<T> {
133
- const { getCookies } = await import('../server/request-context.js');
134
- const jar = await getCookies();
135
- const raw = jar.get(name);
136
- return codec.parse(raw);
283
+ get(): T {
284
+ if (_serverImpl) {
285
+ const jar = _serverImpl.getCookieJar();
286
+ const raw = jar.get(name);
287
+ return codec.parse(raw);
288
+ }
289
+ // Client path
290
+ assertNotHttpOnlyClient('get');
291
+ if (_clientImpl) {
292
+ const raw = _clientImpl.getCookieValue(name);
293
+ return codec.parse(raw);
294
+ }
295
+ throw new Error(
296
+ `[timber] defineCookie('${name}').get() — no environment registered. ` +
297
+ 'Ensure @timber-js/app/server or @timber-js/app/client is imported.'
298
+ );
137
299
  },
138
300
 
139
- async setCookie(value: T): Promise<void> {
140
- const { getCookies } = await import('../server/request-context.js');
141
- const jar = await getCookies();
142
- const serialized = codec.serialize(value);
143
- if (serialized === null) {
301
+ set(value: T): void {
302
+ if (_serverImpl) {
303
+ const jar = _serverImpl.getCookieJar();
304
+ const serialized = codec.serialize(value);
305
+ if (serialized === null) {
306
+ jar.delete(name, {
307
+ path: resolvedOptions.path,
308
+ domain: resolvedOptions.domain,
309
+ });
310
+ } else {
311
+ jar.set(name, serialized, resolvedOptions);
312
+ }
313
+ return;
314
+ }
315
+ // Client path
316
+ assertNotHttpOnlyClient('set');
317
+ if (_clientImpl) {
318
+ const serialized = codec.serialize(value);
319
+ if (serialized === null) {
320
+ _clientImpl.deleteCookieValue(name, getClientOpts());
321
+ } else {
322
+ _clientImpl.setCookieValue(name, serialized, getClientOpts());
323
+ }
324
+ return;
325
+ }
326
+ throw new Error(
327
+ `[timber] defineCookie('${name}').set() — no environment registered. ` +
328
+ 'Ensure @timber-js/app/server or @timber-js/app/client is imported.'
329
+ );
330
+ },
331
+
332
+ delete(): void {
333
+ if (_serverImpl) {
334
+ const jar = _serverImpl.getCookieJar();
144
335
  jar.delete(name, {
145
336
  path: resolvedOptions.path,
146
337
  domain: resolvedOptions.domain,
147
338
  });
148
- } else {
149
- jar.set(name, serialized, resolvedOptions);
339
+ return;
340
+ }
341
+ // Client path
342
+ assertNotHttpOnlyClient('delete');
343
+ if (_clientImpl) {
344
+ _clientImpl.deleteCookieValue(name, getClientOpts());
345
+ return;
150
346
  }
347
+ throw new Error(
348
+ `[timber] defineCookie('${name}').delete() — no environment registered. ` +
349
+ 'Ensure @timber-js/app/server or @timber-js/app/client is imported.'
350
+ );
151
351
  },
352
+ };
152
353
 
153
- async deleteCookie(): Promise<void> {
154
- const { getCookies } = await import('../server/request-context.js');
155
- const jar = await getCookies();
156
- jar.delete(name, {
157
- path: resolvedOptions.path,
158
- domain: resolvedOptions.domain,
159
- });
160
- },
354
+ if (isHttpOnly) {
355
+ return base as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;
356
+ }
357
+
358
+ // httpOnly: false — add useCookie client hook
359
+ const full: CookieDefinition<T> = {
360
+ ...base,
161
361
 
162
362
  useCookie(): [T, (value: T) => void, () => void] {
163
363
  const { useCookie: useRawCookie } = getUseCookieModule();
164
364
 
165
- // Extract client-safe options (no httpOnly — client cookies can't be httpOnly)
166
- const clientOpts: ClientCookieOptions = {
167
- path: resolvedOptions.path,
168
- domain: resolvedOptions.domain,
169
- maxAge: resolvedOptions.maxAge,
170
- expires: resolvedOptions.expires,
171
- sameSite: resolvedOptions.sameSite,
172
- secure: resolvedOptions.secure,
173
- };
174
-
365
+ const clientOpts = getClientOpts();
175
366
  const [raw, setRaw, deleteRaw] = useRawCookie(name, clientOpts);
176
367
  const parsed = codec.parse(raw);
177
368
 
@@ -187,4 +378,6 @@ export function defineCookie<T>(
187
378
  return [parsed, setTyped, deleteRaw];
188
379
  },
189
380
  };
381
+
382
+ return full as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;
190
383
  }
@@ -2,12 +2,15 @@
2
2
  // See design/29-cookies.md §"Typed Cookies with Schema Validation"
3
3
 
4
4
  export { defineCookie } from './define-cookie.js';
5
- export type { CookieDefinition, CookieCodec, DefineCookieOptions } from './define-cookie.js';
5
+ export type {
6
+ CookieDefinition,
7
+ ServerOnlyCookieDefinition,
8
+ CookieCodec,
9
+ DefineCookieOptions,
10
+ } from './define-cookie.js';
6
11
 
7
- // Codec is the canonical home for the Codec type — import from
8
- // @timber-js/app/codec. Re-export removed per TIM-721.
9
-
10
- // cookies() is a server-only function (requires AsyncLocalStorage) and is
11
- // exported from @timber-js/app/server. It is intentionally NOT re-exported
12
- // here so that client-side code importing defineCookie doesn't pull in
13
- // server ALS code. See TIM-704.
12
+ // JSON-in-cookie helper. Wraps any JSON-serializable value with URL-encoding
13
+ // so the stored form satisfies RFC 6265 §4.1.1 cookie-octet (the rule that
14
+ // the framework now enforces in `getCookieJar().set()` to close TIM-868).
15
+ // See ONGOING_SECURITY.md H-3 and design/29-cookies.md.
16
+ export { jsonCookieCodec } from './json-cookie.js';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * jsonCookieCodec — Codec helper for storing JSON-serializable values in
3
+ * cookies.
4
+ *
5
+ * The codec is intentionally minimal: `JSON.stringify` on serialize,
6
+ * `JSON.parse` on parse. The framework handles the URL encoding /
7
+ * decoding around it (see design/29-cookies.md §"Encoding Contract"):
8
+ *
9
+ * defineCookie.setCookie(value)
10
+ * → codec.serialize: JSON.stringify → '{"a":1}'
11
+ * → cookies().set: encodeURIComponent → '%7B%22a%22%3A1%7D'
12
+ * → wire: Set-Cookie: prefs=%7B%22a%22%3A1%7D
13
+ *
14
+ * defineCookie.getCookie()
15
+ * → wire: Cookie: prefs=%7B%22a%22%3A1%7D
16
+ * → parseCookieHeader: decodeURIComponent → '{"a":1}'
17
+ * → codec.parse: JSON.parse → { a: 1 }
18
+ *
19
+ * The same chain works on the client: `useCookie` auto-encodes on writes
20
+ * and auto-decodes on reads, so `jsonCookieCodec` composes with both the
21
+ * server `defineCookie` methods and the client `useCookie` hook with no
22
+ * environment-specific branches.
23
+ *
24
+ * Parsing is total: any non-JSON value (or `undefined`) returns the
25
+ * supplied default. This matches the "never crash on user-controlled
26
+ * cookie input" semantics that `fromSchema` uses for typed search params.
27
+ *
28
+ * ```ts
29
+ * import { defineCookie, jsonCookieCodec } from '@timber-js/app/cookies';
30
+ *
31
+ * interface Prefs { lang: string; fontSize: number }
32
+ *
33
+ * export const prefsCookie = defineCookie('prefs', {
34
+ * codec: jsonCookieCodec<Prefs>({ lang: 'en', fontSize: 16 }),
35
+ * httpOnly: false,
36
+ * maxAge: 60 * 60 * 24 * 365,
37
+ * });
38
+ *
39
+ * await prefsCookie.setCookie({ lang: 'fr', fontSize: 18 });
40
+ * ```
41
+ */
42
+
43
+ import type { Codec } from '../codec.js';
44
+
45
+ /**
46
+ * Build a CookieCodec that JSON-encodes the value. The framework's
47
+ * cookie write path then URL-encodes the JSON string into a valid
48
+ * `cookie-octet` form, and the read path reverses both transforms.
49
+ *
50
+ * @param defaultValue - Returned by `parse` when the cookie is missing
51
+ * or the stored value cannot be JSON-parsed. Optional — if omitted,
52
+ * `parse` returns `undefined` on missing/malformed input.
53
+ *
54
+ * The default is **deep-cloned** on every fallback return via
55
+ * `structuredClone`, so mutating the parsed result in one request
56
+ * cannot leak into later requests that fall back to the same
57
+ * default. This matters for long-lived server processes where a
58
+ * codec is defined once at module load and shared across all
59
+ * requests — without the clone, a user doing
60
+ * `const prefs = prefsCookie.get(); prefs.lang = 'fr'` on a missing
61
+ * cookie would mutate the module-level default and poison every
62
+ * later fallback. See the "shared mutable default" regression test
63
+ * in `tests/define-cookie.test.ts`.
64
+ */
65
+ export function jsonCookieCodec<T>(defaultValue?: T): Codec<T> {
66
+ // Resolve the fallback at call time, not capture time, so every
67
+ // fallback return produces a fresh deep copy. structuredClone handles
68
+ // nested objects, arrays, dates, maps, sets — everything
69
+ // JSON-serializable plus more. For primitives and undefined,
70
+ // structuredClone is effectively a no-op.
71
+ const cloneDefault = (): T => {
72
+ if (defaultValue === undefined || defaultValue === null) {
73
+ return defaultValue as T;
74
+ }
75
+ // structuredClone is a Node built-in since Node 17, available in
76
+ // all supported runtimes.
77
+ return structuredClone(defaultValue);
78
+ };
79
+
80
+ return {
81
+ parse(value: string | string[] | undefined): T {
82
+ if (value === undefined || value === '') {
83
+ return cloneDefault();
84
+ }
85
+ // Cookies are single-valued by name; defensively pick the last
86
+ // entry if a Codec consumer somehow passes an array.
87
+ const raw = Array.isArray(value) ? value[value.length - 1] : value;
88
+ if (raw === undefined || raw === '') {
89
+ return cloneDefault();
90
+ }
91
+ try {
92
+ return JSON.parse(raw) as T;
93
+ } catch {
94
+ return cloneDefault();
95
+ }
96
+ },
97
+
98
+ serialize(value: T): string | null {
99
+ if (value === null || value === undefined) {
100
+ return null;
101
+ }
102
+ return JSON.stringify(value);
103
+ },
104
+ };
105
+ }