theokit 0.43.12 → 0.45.0

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 (305) hide show
  1. package/dist/{actions-virtual-module-RSFRPJQZ.js → actions-virtual-module-67K4POQ2.js} +20 -20
  2. package/dist/actions-virtual-module-67K4POQ2.js.map +1 -0
  3. package/dist/{actions-virtual-module-HG66SZ7T.js → actions-virtual-module-LLDDAQWY.js} +4 -3
  4. package/dist/{actions-virtual-module-HG66SZ7T.js.map → actions-virtual-module-LLDDAQWY.js.map} +1 -1
  5. package/dist/adapters/web-shim.js +1 -1
  6. package/dist/adapters/web-shim.js.map +1 -1
  7. package/dist/adapters/ws-shim.js +1 -1
  8. package/dist/adapters/ws-shim.js.map +1 -1
  9. package/dist/{agent-OXRRKB55.js → agent-WFULT3OB.js} +2 -2
  10. package/dist/{agents-typed-client-ZP7BKJB3.js → agents-typed-client-TNHCUU7Q.js} +2 -2
  11. package/dist/agents-typed-client-TNHCUU7Q.js.map +1 -0
  12. package/dist/{app-typed-client-USLAMN7X.js → app-typed-client-N5WUE2CP.js} +62 -24
  13. package/dist/app-typed-client-N5WUE2CP.js.map +1 -0
  14. package/dist/{app-typed-client-GDF2OMJ2.js → app-typed-client-S3VR4ZA3.js} +47 -8
  15. package/dist/app-typed-client-S3VR4ZA3.js.map +1 -0
  16. package/dist/{body-parser-web-NNEJGX6Y.js → body-parser-web-4Z2QTPDU.js} +2 -2
  17. package/dist/body-parser-web-4Z2QTPDU.js.map +1 -0
  18. package/dist/boot/index.js +4 -4
  19. package/dist/boot/index.js.map +1 -1
  20. package/dist/{broadcast-4SX7R5XE.js → broadcast-H64DJRBE.js} +3 -3
  21. package/dist/broadcast-H64DJRBE.js.map +1 -0
  22. package/dist/{build-MQHIJROR.js → build-YQ6MXOV6.js} +13 -4
  23. package/dist/build-YQ6MXOV6.js.map +1 -0
  24. package/dist/{build-request-body-preview-FVBQ7LCE.js → build-request-body-preview-CKWWBF2M.js} +2 -2
  25. package/dist/build-request-body-preview-CKWWBF2M.js.map +1 -0
  26. package/dist/{chunk-26ECIX74.js → chunk-2ADKT37V.js} +5 -5
  27. package/dist/chunk-2ADKT37V.js.map +1 -0
  28. package/dist/{chunk-JIL74SFK.js → chunk-37ZO643S.js} +9 -9
  29. package/dist/chunk-37ZO643S.js.map +1 -0
  30. package/dist/{chunk-ESC54TAK.js → chunk-3DGIANAN.js} +1 -1
  31. package/dist/chunk-3DGIANAN.js.map +1 -0
  32. package/dist/{chunk-6FK7FKY6.js → chunk-47FFT43U.js} +1 -1
  33. package/dist/chunk-47FFT43U.js.map +1 -0
  34. package/dist/{chunk-ZJQ4O76G.js → chunk-4FWBKX6G.js} +1 -1
  35. package/dist/chunk-4FWBKX6G.js.map +1 -0
  36. package/dist/{chunk-NM4WF3XD.js → chunk-4PBJ266F.js} +1 -1
  37. package/dist/chunk-4PBJ266F.js.map +1 -0
  38. package/dist/{chunk-7MQOHNHE.js → chunk-52PDWL5S.js} +1 -1
  39. package/dist/chunk-52PDWL5S.js.map +1 -0
  40. package/dist/{chunk-575PAX5L.js → chunk-5AMXBU2R.js} +62 -38
  41. package/dist/chunk-5AMXBU2R.js.map +1 -0
  42. package/dist/{chunk-JQJN6LFK.js → chunk-62EU2BVZ.js} +2 -2
  43. package/dist/chunk-62EU2BVZ.js.map +1 -0
  44. package/dist/{chunk-SJIRP73B.js → chunk-646CA6RV.js} +2 -2
  45. package/dist/chunk-646CA6RV.js.map +1 -0
  46. package/dist/{chunk-ASDXVRJD.js → chunk-6KDXP472.js} +4 -4
  47. package/dist/chunk-6KDXP472.js.map +1 -0
  48. package/dist/chunk-6LNIGF5P.js +46 -0
  49. package/dist/chunk-6LNIGF5P.js.map +1 -0
  50. package/dist/{chunk-IE6C4KZX.js → chunk-6VDYQ4I6.js} +11 -496
  51. package/dist/chunk-6VDYQ4I6.js.map +1 -0
  52. package/dist/chunk-7IHWXDJJ.js +1 -0
  53. package/dist/chunk-7IHWXDJJ.js.map +1 -0
  54. package/dist/{chunk-JQSKBMXP.js → chunk-7LVM22VG.js} +1 -1
  55. package/dist/chunk-7LVM22VG.js.map +1 -0
  56. package/dist/{chunk-UVXB2ER7.js → chunk-7SABX4DJ.js} +1 -1
  57. package/dist/chunk-7SABX4DJ.js.map +1 -0
  58. package/dist/{chunk-X32XEJXR.js → chunk-ACKBVBUI.js} +1 -1
  59. package/dist/chunk-ACKBVBUI.js.map +1 -0
  60. package/dist/{chunk-UUFYP2UM.js → chunk-AM3QABW5.js} +3 -3
  61. package/dist/chunk-AM3QABW5.js.map +1 -0
  62. package/dist/{chunk-FH3DYBAN.js → chunk-ANS4ELRH.js} +1 -1
  63. package/dist/chunk-ANS4ELRH.js.map +1 -0
  64. package/dist/{chunk-UD3LFGDL.js → chunk-AVQU5CQF.js} +1 -1
  65. package/dist/chunk-AVQU5CQF.js.map +1 -0
  66. package/dist/{chunk-CJNMJUHE.js → chunk-B7ONQ4QJ.js} +2 -2
  67. package/dist/{chunk-WSJKACWB.js → chunk-C34PMLSN.js} +1 -1
  68. package/dist/chunk-C34PMLSN.js.map +1 -0
  69. package/dist/{chunk-DJFMAWH5.js → chunk-EIRQM55C.js} +6 -5
  70. package/dist/chunk-EIRQM55C.js.map +1 -0
  71. package/dist/{chunk-ZEGYW52B.js → chunk-FB2HBNIC.js} +1 -1
  72. package/dist/chunk-FB2HBNIC.js.map +1 -0
  73. package/dist/chunk-FXZZS7YW.js +605 -0
  74. package/dist/chunk-FXZZS7YW.js.map +1 -0
  75. package/dist/{chunk-HGUZWG2C.js → chunk-FZG3JYB3.js} +8 -47
  76. package/dist/chunk-FZG3JYB3.js.map +1 -0
  77. package/dist/{chunk-V3LJN2GS.js → chunk-GAPBPRPP.js} +1 -1
  78. package/dist/chunk-GAPBPRPP.js.map +1 -0
  79. package/dist/chunk-GUBN5UTT.js +1 -0
  80. package/dist/chunk-GUBN5UTT.js.map +1 -0
  81. package/dist/{chunk-6OXCZMW3.js → chunk-HEK4SET7.js} +5 -5
  82. package/dist/chunk-HEK4SET7.js.map +1 -0
  83. package/dist/{chunk-N2D5AVRR.js → chunk-HRCNHWLP.js} +7 -109
  84. package/dist/chunk-HRCNHWLP.js.map +1 -0
  85. package/dist/{chunk-C3ZZ56YZ.js → chunk-JA2ZAT5Q.js} +1 -1
  86. package/dist/chunk-JA2ZAT5Q.js.map +1 -0
  87. package/dist/{chunk-L2S2R3TI.js → chunk-KBGGQK4I.js} +25 -8
  88. package/dist/chunk-KBGGQK4I.js.map +1 -0
  89. package/dist/{chunk-VPISAMXU.js → chunk-KD5G3YXI.js} +4 -4
  90. package/dist/chunk-KD5G3YXI.js.map +1 -0
  91. package/dist/{chunk-VMEWD57H.js → chunk-KPKLIUUL.js} +1 -1
  92. package/dist/chunk-KPKLIUUL.js.map +1 -0
  93. package/dist/{chunk-4HAU7AJM.js → chunk-L4Y6RJ4E.js} +3 -3
  94. package/dist/chunk-L4Y6RJ4E.js.map +1 -0
  95. package/dist/{chunk-OVHW23YC.js → chunk-L5Z6IY5U.js} +5 -5
  96. package/dist/chunk-L5Z6IY5U.js.map +1 -0
  97. package/dist/{chunk-JBCHWRKF.js → chunk-MTEWLGVY.js} +2 -2
  98. package/dist/chunk-MTEWLGVY.js.map +1 -0
  99. package/dist/{chunk-XZ6B2Y2R.js → chunk-MWP4YXWJ.js} +1 -1
  100. package/dist/chunk-MWP4YXWJ.js.map +1 -0
  101. package/dist/{chunk-5QW7IQQU.js → chunk-NTDN54XO.js} +1 -1
  102. package/dist/chunk-NTDN54XO.js.map +1 -0
  103. package/dist/{chunk-7BIM27LV.js → chunk-OIGJOYX6.js} +1 -1
  104. package/dist/chunk-OIGJOYX6.js.map +1 -0
  105. package/dist/{chunk-TSLSIRBR.js → chunk-OLGPYOLO.js} +2 -2
  106. package/dist/chunk-OLGPYOLO.js.map +1 -0
  107. package/dist/{chunk-6NEXVBPY.js → chunk-RWNSW4FM.js} +1 -1
  108. package/dist/chunk-RWNSW4FM.js.map +1 -0
  109. package/dist/{chunk-E25FBYJP.js → chunk-SAFGDIPQ.js} +1 -1
  110. package/dist/chunk-SAFGDIPQ.js.map +1 -0
  111. package/dist/{chunk-OP7QCTR5.js → chunk-SEELKQUM.js} +2 -2
  112. package/dist/chunk-SEELKQUM.js.map +1 -0
  113. package/dist/{chunk-4B2ZMX45.js → chunk-SSYDEKWF.js} +1 -1
  114. package/dist/chunk-SSYDEKWF.js.map +1 -0
  115. package/dist/{chunk-DQVBL2G4.js → chunk-SZU6BOMV.js} +75 -53
  116. package/dist/chunk-SZU6BOMV.js.map +1 -0
  117. package/dist/{chunk-65EFIUDD.js → chunk-U267AOWS.js} +1 -1
  118. package/dist/chunk-U267AOWS.js.map +1 -0
  119. package/dist/{chunk-FX72VHAE.js → chunk-U3H53IIN.js} +6 -6
  120. package/dist/chunk-U3H53IIN.js.map +1 -0
  121. package/dist/{chunk-OPLICDUB.js → chunk-UW2TEAGR.js} +1 -1
  122. package/dist/chunk-UW2TEAGR.js.map +1 -0
  123. package/dist/{chunk-2WH6SD3C.js → chunk-UYMCKHML.js} +4 -4
  124. package/dist/chunk-UYMCKHML.js.map +1 -0
  125. package/dist/{chunk-OI6DK6ZJ.js → chunk-VFU7DTUL.js} +6 -4
  126. package/dist/chunk-VFU7DTUL.js.map +1 -0
  127. package/dist/{chunk-IAJ2JVEH.js → chunk-VSLTKX5L.js} +1 -1
  128. package/dist/chunk-VSLTKX5L.js.map +1 -0
  129. package/dist/{chunk-YB5DJJ4U.js → chunk-VZTYCAO6.js} +1 -1
  130. package/dist/chunk-VZTYCAO6.js.map +1 -0
  131. package/dist/chunk-WDGYCBNI.js +65 -0
  132. package/dist/chunk-WDGYCBNI.js.map +1 -0
  133. package/dist/chunk-WPJOTQA5.js +1 -0
  134. package/dist/chunk-WPJOTQA5.js.map +1 -0
  135. package/dist/{chunk-DGUM43GV.js → chunk-XTHN6JBX.js} +1 -1
  136. package/dist/chunk-XTHN6JBX.js.map +1 -0
  137. package/dist/{chunk-HZ6USUHC.js → chunk-YOIVCCZT.js} +1 -1
  138. package/dist/chunk-YOIVCCZT.js.map +1 -0
  139. package/dist/cli/index.js +8 -8
  140. package/dist/client/core.d.ts +5 -259
  141. package/dist/client/core.js +13 -9
  142. package/dist/client/core.js.map +1 -1
  143. package/dist/client/index.d.ts +7 -96
  144. package/dist/client/index.js +19 -67
  145. package/dist/client/index.js.map +1 -1
  146. package/dist/{controller-swc-transform-JJRMTSDY.js → controller-swc-transform-T7ZTD55V.js} +2 -2
  147. package/dist/controller-swc-transform-T7ZTD55V.js.map +1 -0
  148. package/dist/{cookies-MJKMGJ7N.js → cookies-SOXUQJXG.js} +3 -3
  149. package/dist/cookies-SOXUQJXG.js.map +1 -0
  150. package/dist/{dev-WQ4LWCAW.js → dev-YNO26JS3.js} +8 -7
  151. package/dist/{dev-WQ4LWCAW.js.map → dev-YNO26JS3.js.map} +1 -1
  152. package/dist/{dev-emit-HLGJK3PP.js → dev-emit-F54Y5TIU.js} +5 -5
  153. package/dist/dev-emit-F54Y5TIU.js.map +1 -0
  154. package/dist/devtools/entry.js +61 -53
  155. package/dist/devtools/entry.js.map +1 -1
  156. package/dist/dispatcher-MXKF3CRH.js +8 -0
  157. package/dist/dispatcher-MXKF3CRH.js.map +1 -0
  158. package/dist/index.js +25 -25
  159. package/dist/index.js.map +1 -1
  160. package/dist/{info-75U6GCMD.js → info-ICDTPJDC.js} +3 -3
  161. package/dist/{internal-api-JFORKYLR.js → internal-api-NO24QAXC.js} +10 -8
  162. package/dist/{internal-api-DV3H5NNV.js → internal-api-S7WK2WCY.js} +20 -20
  163. package/dist/internal-api-S7WK2WCY.js.map +1 -0
  164. package/dist/{load-config-XP7NSSA7.js → load-config-ICCNMO74.js} +3 -3
  165. package/dist/{mcp-FF3NTCHB.js → mcp-3S6A4A4Q.js} +3 -3
  166. package/dist/{openapi-ZPXYRAM2.js → openapi-NIYA2VBG.js} +3 -3
  167. package/dist/react-query/index.js +2 -2
  168. package/dist/react-query/index.js.map +1 -1
  169. package/dist/{routes-ZVDKABXZ.js → routes-2RD4S5PZ.js} +3 -3
  170. package/dist/{schema-FBOTN46F.js → schema-S553QCAU.js} +2 -2
  171. package/dist/server/agent/index.d.ts +3 -72
  172. package/dist/server/agent/index.js +4 -4
  173. package/dist/server/agent/index.js.map +1 -1
  174. package/dist/server/auth/index.js +4 -4
  175. package/dist/server/auth/index.js.map +1 -1
  176. package/dist/server/cost/index.js +2 -2
  177. package/dist/server/cost/index.js.map +1 -1
  178. package/dist/server/cron/index.js +6 -6
  179. package/dist/server/cron/index.js.map +1 -1
  180. package/dist/server/define/index.d.ts +2 -2
  181. package/dist/server/define/index.js +4 -4
  182. package/dist/server/define/index.js.map +1 -1
  183. package/dist/server/http/index.js +11 -11
  184. package/dist/server/http/index.js.map +1 -1
  185. package/dist/server/index.d.ts +3 -3
  186. package/dist/server/index.js +41 -41
  187. package/dist/server/index.js.map +1 -1
  188. package/dist/server/jobs/index.js +7 -7
  189. package/dist/server/jobs/index.js.map +1 -1
  190. package/dist/server/observability/index.d.ts +8 -1
  191. package/dist/server/observability/index.js +5 -5
  192. package/dist/server/observability/index.js.map +1 -1
  193. package/dist/server/plugins/index.js +3 -3
  194. package/dist/server/plugins/index.js.map +1 -1
  195. package/dist/server/rate-limit/index.js +4 -4
  196. package/dist/server/rate-limit/index.js.map +1 -1
  197. package/dist/server/realtime/index.js +2 -2
  198. package/dist/server/realtime/index.js.map +1 -1
  199. package/dist/server/scan/index.js +7 -7
  200. package/dist/server/scan/index.js.map +1 -1
  201. package/dist/server/security/index.js +5 -5
  202. package/dist/server/security/index.js.map +1 -1
  203. package/dist/server/storage/index.js +2 -2
  204. package/dist/server/storage/index.js.map +1 -1
  205. package/dist/server/webhook/index.js +3 -3
  206. package/dist/server/webhook/index.js.map +1 -1
  207. package/dist/server-error-to-envelope-UM7DEVM4.js +8 -0
  208. package/dist/server-error-to-envelope-UM7DEVM4.js.map +1 -0
  209. package/dist/{server-routes-hmr-RI7SYNT2.js → server-routes-hmr-6CRNSEWF.js} +2 -2
  210. package/dist/server-routes-hmr-6CRNSEWF.js.map +1 -0
  211. package/dist/{services-typed-client-AU6DKXAO.js → services-typed-client-IXNOPVZA.js} +3 -3
  212. package/dist/services-typed-client-IXNOPVZA.js.map +1 -0
  213. package/dist/{start-2U3FUCJX.js → start-TVT7BZ4V.js} +35 -11
  214. package/dist/start-TVT7BZ4V.js.map +1 -0
  215. package/dist/vite-plugin/index.js +25 -25
  216. package/dist/vite-plugin/index.js.map +1 -1
  217. package/dist/{vite-plugin-D2NTPZNC.js → vite-plugin-K64KQZSU.js} +8 -7
  218. package/package.json +11 -10
  219. package/dist/actions-virtual-module-RSFRPJQZ.js.map +0 -1
  220. package/dist/agents-typed-client-ZP7BKJB3.js.map +0 -1
  221. package/dist/app-typed-client-GDF2OMJ2.js.map +0 -1
  222. package/dist/app-typed-client-USLAMN7X.js.map +0 -1
  223. package/dist/body-parser-web-NNEJGX6Y.js.map +0 -1
  224. package/dist/build-MQHIJROR.js.map +0 -1
  225. package/dist/build-request-body-preview-FVBQ7LCE.js.map +0 -1
  226. package/dist/chunk-26ECIX74.js.map +0 -1
  227. package/dist/chunk-2WH6SD3C.js.map +0 -1
  228. package/dist/chunk-4B2ZMX45.js.map +0 -1
  229. package/dist/chunk-4HAU7AJM.js.map +0 -1
  230. package/dist/chunk-575PAX5L.js.map +0 -1
  231. package/dist/chunk-5QW7IQQU.js.map +0 -1
  232. package/dist/chunk-65EFIUDD.js.map +0 -1
  233. package/dist/chunk-6FK7FKY6.js.map +0 -1
  234. package/dist/chunk-6NEXVBPY.js.map +0 -1
  235. package/dist/chunk-6OXCZMW3.js.map +0 -1
  236. package/dist/chunk-7BIM27LV.js.map +0 -1
  237. package/dist/chunk-7MQOHNHE.js.map +0 -1
  238. package/dist/chunk-ASDXVRJD.js.map +0 -1
  239. package/dist/chunk-C3ZZ56YZ.js.map +0 -1
  240. package/dist/chunk-DJFMAWH5.js.map +0 -1
  241. package/dist/chunk-DQVBL2G4.js.map +0 -1
  242. package/dist/chunk-E25FBYJP.js.map +0 -1
  243. package/dist/chunk-E3JH6YUM.js +0 -1
  244. package/dist/chunk-ESC54TAK.js.map +0 -1
  245. package/dist/chunk-FH3DYBAN.js.map +0 -1
  246. package/dist/chunk-FX72VHAE.js.map +0 -1
  247. package/dist/chunk-HGUZWG2C.js.map +0 -1
  248. package/dist/chunk-HZ6USUHC.js.map +0 -1
  249. package/dist/chunk-IAJ2JVEH.js.map +0 -1
  250. package/dist/chunk-IE6C4KZX.js.map +0 -1
  251. package/dist/chunk-JBCHWRKF.js.map +0 -1
  252. package/dist/chunk-JHEAWR3L.js +0 -1
  253. package/dist/chunk-JIL74SFK.js.map +0 -1
  254. package/dist/chunk-JQJN6LFK.js.map +0 -1
  255. package/dist/chunk-JQSKBMXP.js.map +0 -1
  256. package/dist/chunk-KPRJS3II.js +0 -505
  257. package/dist/chunk-KPRJS3II.js.map +0 -1
  258. package/dist/chunk-L2S2R3TI.js.map +0 -1
  259. package/dist/chunk-N2D5AVRR.js.map +0 -1
  260. package/dist/chunk-NM4WF3XD.js.map +0 -1
  261. package/dist/chunk-NPMCKOX4.js +0 -1
  262. package/dist/chunk-NPMCKOX4.js.map +0 -1
  263. package/dist/chunk-OI6DK6ZJ.js.map +0 -1
  264. package/dist/chunk-OP7QCTR5.js.map +0 -1
  265. package/dist/chunk-OPLICDUB.js.map +0 -1
  266. package/dist/chunk-OVHW23YC.js.map +0 -1
  267. package/dist/chunk-SJIRP73B.js.map +0 -1
  268. package/dist/chunk-TSLSIRBR.js.map +0 -1
  269. package/dist/chunk-UD3LFGDL.js.map +0 -1
  270. package/dist/chunk-UUFYP2UM.js.map +0 -1
  271. package/dist/chunk-UVXB2ER7.js.map +0 -1
  272. package/dist/chunk-V3LJN2GS.js.map +0 -1
  273. package/dist/chunk-VMEWD57H.js.map +0 -1
  274. package/dist/chunk-VPISAMXU.js.map +0 -1
  275. package/dist/chunk-WSJKACWB.js.map +0 -1
  276. package/dist/chunk-X32XEJXR.js.map +0 -1
  277. package/dist/chunk-XZ6B2Y2R.js.map +0 -1
  278. package/dist/chunk-YB5DJJ4U.js.map +0 -1
  279. package/dist/chunk-ZEGYW52B.js.map +0 -1
  280. package/dist/chunk-ZJQ4O76G.js.map +0 -1
  281. package/dist/controller-swc-transform-JJRMTSDY.js.map +0 -1
  282. package/dist/cookies-MJKMGJ7N.js.map +0 -1
  283. package/dist/dev-emit-HLGJK3PP.js.map +0 -1
  284. package/dist/dispatcher-DLKIXYNO.js +0 -8
  285. package/dist/dispatcher-DLKIXYNO.js.map +0 -1
  286. package/dist/internal-api-DV3H5NNV.js.map +0 -1
  287. package/dist/internal-api-JFORKYLR.js.map +0 -1
  288. package/dist/load-config-XP7NSSA7.js.map +0 -1
  289. package/dist/schema-FBOTN46F.js.map +0 -1
  290. package/dist/server-error-to-envelope-EOFUIUNB.js +0 -8
  291. package/dist/server-error-to-envelope-EOFUIUNB.js.map +0 -1
  292. package/dist/server-routes-hmr-RI7SYNT2.js.map +0 -1
  293. package/dist/services-typed-client-AU6DKXAO.js.map +0 -1
  294. package/dist/start-2U3FUCJX.js.map +0 -1
  295. package/dist/vite-plugin-D2NTPZNC.js.map +0 -1
  296. /package/dist/{agent-OXRRKB55.js.map → agent-WFULT3OB.js.map} +0 -0
  297. /package/dist/{chunk-CJNMJUHE.js.map → chunk-B7ONQ4QJ.js.map} +0 -0
  298. /package/dist/{info-75U6GCMD.js.map → info-ICDTPJDC.js.map} +0 -0
  299. /package/dist/{broadcast-4SX7R5XE.js.map → internal-api-NO24QAXC.js.map} +0 -0
  300. /package/dist/{chunk-DGUM43GV.js.map → load-config-ICCNMO74.js.map} +0 -0
  301. /package/dist/{mcp-FF3NTCHB.js.map → mcp-3S6A4A4Q.js.map} +0 -0
  302. /package/dist/{openapi-ZPXYRAM2.js.map → openapi-NIYA2VBG.js.map} +0 -0
  303. /package/dist/{routes-ZVDKABXZ.js.map → routes-2RD4S5PZ.js.map} +0 -0
  304. /package/dist/{chunk-E3JH6YUM.js.map → schema-S553QCAU.js.map} +0 -0
  305. /package/dist/{chunk-JHEAWR3L.js.map → vite-plugin-K64KQZSU.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/auth/auth.ts","../src/server/auth/crypto.ts","../src/server/auth/session.ts","../src/server/_internal/encoding.ts","../src/server/auth/auth-totp.ts","../src/server/auth/auth-backup-codes.ts","../src/server/auth/auth-throttle.ts","../src/server/auth/oauth-pkce.ts","../src/server/auth/oauth-state.ts","../src/server/auth/oidc-discovery.ts"],"sourcesContent":["export class AuthRequiredError extends Error {\n code = 'AUTH_REQUIRED' as const\n status = 401\n\n constructor(message = 'Authentication required') {\n super(message)\n this.name = 'AuthRequiredError'\n }\n}\n\nexport function requireAuth<T>(session: T | null | undefined): asserts session is T {\n if (session === null || session === undefined) {\n throw new AuthRequiredError()\n }\n}\n","// AES-GCM-256 token encryption with cached key derivation.\n//\n// CR-002 fixes:\n// - KDF: HKDF (RFC 5869) over the raw secret, not raw SHA-256.\n// HKDF binds the application context (\"theo-session-v1\") into the\n// derived key so the same secret used elsewhere cannot decrypt these\n// tokens. SHA-256 of the raw secret has neither domain separation nor\n// proper key-stretching guarantees.\n// - Cache: derived `CryptoKey` is memoized per (secret, info) tuple.\n// Pre-fix, every encrypt/decrypt re-ran SHA-256 + importKey, which (a)\n// burned CPU on every authenticated request and (b) made\n// `decryptWithFallback` leak rotation-array position via timing.\n// - Buffer typing: `Uint8Array` flows directly into Web Crypto\n// (`BufferSource`-compatible). The previous `as unknown as ArrayBuffer`\n// double-cast (CR-016) is removed.\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nconst HKDF_INFO_DEFAULT = encoder.encode('theo-session-v1')\nconst HKDF_SALT_EMPTY = new Uint8Array(0)\n\n// Memoize derived keys. Bounded by the number of distinct secrets the app\n// ever uses (typically 1 in steady state, 2-5 during rotation). Map-based\n// so distinct secrets do not evict each other.\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const cached = keyCache.get(secret)\n if (cached) return cached\n\n const derive = (async () => {\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HKDF' },\n false,\n ['deriveKey'],\n )\n return crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: HKDF_SALT_EMPTY, info: HKDF_INFO_DEFAULT },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n })()\n\n keyCache.set(secret, derive)\n // If derivation fails (invalid secret), evict so a retry can try fresh.\n derive.catch(() => {\n keyCache.delete(secret)\n })\n return derive\n}\n\nfunction toBase64Url(data: Uint8Array): string {\n return Buffer.from(data).toString('base64url')\n}\n\n/**\n * Decode a base64url string into a `Uint8Array<ArrayBuffer>`. The explicit\n * generic matters: Web Crypto APIs expect `BufferSource`, and TS 5.7+\n * tightened `Uint8Array` to `Uint8Array<ArrayBufferLike>` which includes\n * `SharedArrayBuffer`. Returning the narrower generic lets the value pass\n * `crypto.subtle.decrypt` without `as` casts (CR-016 follow-through).\n */\nfunction fromBase64Url(str: string): Uint8Array<ArrayBuffer> {\n const buf = Buffer.from(str, 'base64url')\n const copy = new Uint8Array(buf.length)\n copy.set(buf)\n return copy\n}\n\n// The `T` generic exists so call sites get type-safe round-tripping\n// (`encrypt<Session>(s, ...)` pairs with `decrypt<Session>(...)`); the\n// runtime body just stringifies `data`, hence T does not appear in the\n// signature beyond the argument.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the round-trip contract with decrypt<T>\nexport async function encrypt<T>(data: T, secret: string): Promise<string> {\n const key = await deriveKey(secret)\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = encoder.encode(JSON.stringify(data))\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext),\n )\n return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`\n}\n\nexport async function decrypt<T>(token: string, secret: string): Promise<T | null> {\n try {\n const parts = token.split(':')\n if (parts.length !== 2) return null\n\n const iv = fromBase64Url(parts[0])\n const ciphertext = fromBase64Url(parts[1])\n const key = await deriveKey(secret)\n\n const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext)\n return JSON.parse(decoder.decode(plaintext)) as T\n } catch {\n return null\n }\n}\n\n/**\n * Reset the derived-key cache. Test helper — never call from production code.\n * Used by Vitest to guarantee isolation between tests that use distinct\n * secrets and want to confirm the derivation path runs.\n */\nexport function _resetKeyCacheForTests(): void {\n keyCache.clear()\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport {\n getCookie,\n setCookie,\n deleteCookie,\n getCookieFromRequest,\n appendCookieToHeaders,\n appendDeleteCookieToHeaders,\n} from '../http/cookies.js'\n\nimport { encrypt, decrypt } from './crypto.js'\n\nexport interface SessionConfig {\n /**\n * Session secret. Either a single string (legacy) or an array of strings\n * where index 0 is the newest. The array form enables dual-key rotation\n * (T3.1 / ADR D5): encrypt always uses `secrets[0]`, decrypt walks the\n * array, transparent re-encrypt on legacy hits (T3.2).\n *\n * EC-1: array length capped at 5 — enforced via throw at construction.\n */\n secret: string | string[]\n cookieName?: string\n maxAge?: number\n}\n\ninterface SessionEnvelope<T> {\n data: T\n exp: number\n}\n\nexport interface SessionMeta {\n /** Index of the secret that decrypted the session. 0 = newest; > 0 = legacy. */\n secretIndex: number\n /** True when the decrypt used a legacy secret and the cookie should be re-encrypted. */\n needsReencrypt: boolean\n}\n\nexport interface SessionManager<TSession> {\n getSession(req: IncomingMessage): Promise<TSession | null>\n /**\n * T3.2 — Variant of `getSession` that surfaces decrypt metadata.\n * Used by `api-middleware.ts` to wire transparent re-encrypt BEFORE\n * the handler runs (so streaming SSR routes don't miss the Set-Cookie\n * window — EC-4).\n */\n getSessionWithMeta(req: IncomingMessage): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(res: ServerResponse, data: TSession): Promise<void>\n destroySession(res: ServerResponse): void\n /**\n * T3.3 — OWASP A07:2021 session fixation mitigation. Re-encrypts the\n * current session with a fresh IV and refreshed expiry. No-op when no\n * session is present.\n */\n rotateSession(req: IncomingMessage, res: ServerResponse): Promise<TSession | null>\n}\n\n/**\n * EC-1 — array length cap. Enforced via throw at construction. Silent\n * truncation would create false sense of rotation; silent acceptance\n * would create unbounded CPU on legacy decrypt walks.\n */\nconst MAX_SECRETS = 5\n\n/**\n * Validate + normalize the secret config into a non-empty array of\n * strings ≥ 32 chars each.\n */\nfunction normalizeSecrets(input: string | string[]): string[] {\n const arr = Array.isArray(input) ? input.slice() : [input]\n if (arr.length === 0) {\n throw new Error('Session secret must be non-empty (received an empty array)')\n }\n if (arr.length > MAX_SECRETS) {\n throw new Error(\n `Session secret array exceeds maximum of ${MAX_SECRETS} entries — drop the oldest before adding a new one (received ${arr.length})`,\n )\n }\n const wasArray = Array.isArray(input)\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n if (typeof s !== 'string' || s.length < 32) {\n const where = wasArray ? ` at index ${i}` : ''\n throw new Error(`Session secret${where} must be at least 32 characters for secure encryption`)\n }\n }\n return arr\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — module-level pure helpers extracted so\n * `createSessionManager` (IncomingMessage path) and\n * `createSessionManagerWeb` (Web path) share the same encryption +\n * dual-key rotation logic without code duplication (sonarjs\n * no-identical-functions). Both wrappers pass `secrets` + `maxAge`\n * captured from their respective factory scope.\n */\nasync function encryptSessionEnvelope(\n data: unknown,\n secrets: string[],\n maxAge: number,\n): Promise<string> {\n const envelope: SessionEnvelope<unknown> = {\n data,\n exp: Date.now() + maxAge * 1000,\n }\n return encrypt(envelope, secrets[0]) // newest\n}\n\n/**\n * CR-002 constant-time: try EVERY rotation entry in parallel, then pick\n * the newest match. Sequential early-exit leaked rotation position via\n * timing (success on entry 0 returned in ~3 ms; success on entry 4 in\n * ~15 ms). Running in parallel binds wall-clock to the slowest single\n * attempt, independent of which entry actually matched. With derived-key\n * cache (CR-002 in crypto.ts), the CPU multiplier per request is ~0\n * after the first request per secret.\n */\nasync function decryptSessionWithFallback<TSession>(\n raw: string,\n secrets: string[],\n): Promise<{ envelope: SessionEnvelope<TSession>; index: number } | null> {\n const results = await Promise.all(\n secrets.map(async (secret, i) => {\n const env = await decrypt<SessionEnvelope<TSession>>(raw, secret)\n return env ? { envelope: env, index: i } : null\n }),\n )\n for (const result of results) {\n if (result) return result\n }\n return null\n}\n\nexport function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookie(res: ServerResponse, token: string): void {\n setCookie(res, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(req) {\n const { data } = await this.getSessionWithMeta(req)\n return data\n },\n\n async getSessionWithMeta(req) {\n const raw = getCookie(req, cookieName)\n if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(res, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n },\n\n destroySession(res) {\n deleteCookie(res, cookieName)\n },\n\n async rotateSession(req, res) {\n const { data } = await this.getSessionWithMeta(req)\n if (data === null) return null\n // Fresh IV per encrypt call (Web Crypto guarantee — see crypto.ts)\n // + refreshed expiry. Last write wins for the cookie value.\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards session manager.\n *\n * Mirror of `SessionManager<TSession>` for the Web `Request` + `Headers`\n * shape. Same `SessionConfig`, same encryption (`encrypt`/`decrypt` from\n * `./crypto.js`), same dual-key rotation logic (CR-002 constant-time\n * decrypt fallback walk + transparent re-encrypt), same OWASP A07\n * `rotateSession` invariant.\n *\n * **Signature differences vs `SessionManager`:**\n * - Read methods take `Request` (instead of `IncomingMessage`).\n * - Write methods take a `Headers` instance to mutate (instead of\n * `ServerResponse`) — caller passes the headers they're building\n * for their `Response` constructor.\n *\n * Uses `getCookieFromRequest` / `appendCookieToHeaders` /\n * `appendDeleteCookieToHeaders` from Phase B slice 6/6.\n */\nexport interface SessionManagerWeb<TSession> {\n getSession(request: Request): Promise<TSession | null>\n getSessionWithMeta(request: Request): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(target: Headers, data: TSession): Promise<void>\n destroySession(target: Headers): void\n rotateSession(request: Request, target: Headers): Promise<TSession | null>\n}\n\nexport function createSessionManagerWeb<TSession>(\n config: SessionConfig,\n): SessionManagerWeb<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookieWeb(target: Headers, token: string): void {\n appendCookieToHeaders(target, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(request) {\n const { data } = await this.getSessionWithMeta(request)\n return data\n },\n\n async getSessionWithMeta(request) {\n const raw = getCookieFromRequest(request, cookieName)\n if (raw === undefined) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(target, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n },\n\n destroySession(target) {\n appendDeleteCookieToHeaders(target, cookieName)\n },\n\n async rotateSession(request, target) {\n const { data } = await this.getSessionWithMeta(request)\n if (data === null) return null\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards transparent re-encrypt helper.\n *\n * Mirror of `rotateIfNeeded(sm, req, res)` for the Web Request + Headers\n * shape. Reads session via `getSessionWithMeta`; if the cookie was\n * decrypted with a legacy secret (index > 0), re-issues the cookie with\n * the newest secret via `createSession(target, data)`.\n *\n * **EC-4 honest framing:** unlike the IncomingMessage path where the\n * timing constraint is \"before `res.writeHead` fires\", the Web Request\n * path's constraint is \"before the `Response` object is constructed and\n * returned to the runtime\". Caller MUST invoke `rotateIfNeededWeb`\n * BEFORE building the final `Response(body, { headers: target })` so the\n * re-encrypted Set-Cookie lands on the wire.\n */\nexport async function rotateIfNeededWeb<TSession>(\n sm: SessionManagerWeb<TSession>,\n request: Request,\n target: Headers,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(request)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(target, data)\n }\n return data\n}\n\n/**\n * T3.2 — Transparent re-encrypt helper (EC-4 timing-safe wrapper).\n *\n * Call this in your `createContext` (before any rendering / streaming\n * starts). It:\n * 1. Reads the session via `getSessionWithMeta`.\n * 2. If the cookie was decrypted with a legacy secret (index > 0), it\n * immediately re-issues the cookie with `secrets[0]` so the next\n * request lands on the newest key.\n * 3. Returns the session data (or null) for use downstream.\n *\n * EC-4: this MUST run before `renderToPipeableStream` / `res.writeHead`\n * fires. Calling it inside an SSR component or handler body that has\n * already streamed bytes makes the re-encrypt a silent no-op (Set-Cookie\n * is locked once headers commit), trapping users on the legacy secret\n * forever once it's removed from the array.\n */\nexport async function rotateIfNeeded<TSession>(\n sm: SessionManager<TSession>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(req)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(res, data)\n }\n return data\n}\n\n/**\n * EC-2 — Production secret guard. Refuses to boot when `NODE_ENV === 'production'`\n * AND the secret is too short (< 32 chars) OR matches a known placeholder.\n *\n * Accepts a single secret OR an array. In array form, each entry is validated\n * independently — a single bad entry refuses boot with an index-qualified message.\n */\nconst PLACEHOLDER_PATTERN = /CHANGE_ME|demo[-_]|placeholder/i\n\nexport function assertProductionSecret(secret: string | string[]): void {\n const arr = Array.isArray(secret) ? secret : [secret]\n const isProd = process.env.NODE_ENV === 'production'\n\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n const isPlaceholder = PLACEHOLDER_PATTERN.test(s)\n const isTooShort = s.length < 32\n const prefix = arr.length > 1 ? `Session secret at index ${i}` : 'Session secret'\n\n if (isProd) {\n if (isTooShort) {\n throw new Error(\n `${prefix} too short for production (${s.length} chars; minimum 32). ` +\n `Set a 32+ random char secret in your env (e.g., \\`openssl rand -hex 32\\`).`,\n )\n }\n if (isPlaceholder) {\n throw new Error(\n `${prefix} looks like a placeholder (\"${s.slice(0, 16)}…\") and NODE_ENV is \"production\". ` +\n `Replace it with a 32+ random char secret (e.g., \\`openssl rand -hex 32\\`) before deploying.`,\n )\n }\n continue\n }\n if (isPlaceholder || isTooShort) {\n console.warn(\n `[theokit] WARNING: ${prefix.toLowerCase()} is a placeholder or too short. ` +\n `This is OK for dev, but the production server will REFUSE to boot until you replace it.`,\n )\n }\n }\n}\n","/**\n * Shared encoding utilities for crypto-adjacent code.\n *\n * CR-020 DRY: `base64urlEncode` was duplicated across `oauth-pkce.ts` and\n * `oauth-state.ts`. Duplication in cryptographic helpers is a bug class —\n * a future RFC-driven tweak (e.g., padding semantics) would diverge across\n * files. Centralizing the single canonical implementation eliminates that\n * vector.\n *\n * Module is intentionally under `_internal/` so it is **not** part of the\n * public API surface. Consumers outside `packages/theo/src/server` must\n * not import from here.\n */\n\n/**\n * RFC 4648 §5 — base64url-encode bytes without padding. Used wherever an\n * RFC requires URL-safe base64 (PKCE per RFC 7636 §4.1, JWT per RFC 7515,\n * etc.). Pure function — does not mutate `bytes`.\n */\nexport function base64urlEncode(bytes: Uint8Array): string {\n let s = ''\n for (const b of bytes) s += String.fromCharCode(b)\n // The `=+$` trailing-padding regex is bounded (at most 2 chars for\n // base64). sonarjs flags the `+` quantifier conservatively, but the\n // input is the output of `btoa()` — a string with at most 2 trailing\n // `=` characters by spec.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded trailing-padding pattern (max 2 chars)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Constant-time string equality.\n *\n * CR-012: prior implementations early-exited on length mismatch\n * (`if (a.length !== b.length) return false`). That early exit leaks\n * the comparison-target length via wall-clock timing — observable when\n * the secret length is itself sensitive (length-based bucket assignment,\n * variable-length nonces). This implementation always iterates\n * `max(a.length, b.length)` characters, OR-folding mismatches into a\n * single accumulator that is checked once at the end.\n *\n * Returns `false` for empty inputs as a defensive default — never accept\n * \"both empty\" as a match.\n */\nexport function constantTimeEquals(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false\n if (a.length === 0 || b.length === 0) return false\n\n const maxLen = Math.max(a.length, b.length)\n let diff = a.length ^ b.length\n for (let i = 0; i < maxLen; i++) {\n // charCodeAt returns NaN for out-of-range indices; (NaN ^ x) is x, so we\n // explicitly substitute 0 to keep the OR-fold meaningful.\n const ca = i < a.length ? a.charCodeAt(i) : 0\n const cb = i < b.length ? b.charCodeAt(i) : 0\n diff |= ca ^ cb\n }\n return diff === 0\n}\n","/**\n * T6.2 — RFC 6238 TOTP primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc6238\n * HMAC-based one-time password (HOTP) base: https://datatracker.ietf.org/doc/html/rfc4226\n *\n * Pure-function, dependency-free implementation via Web Crypto. Secrets\n * may be passed as Uint8Array (preferred) or base32 string.\n *\n * SECURITY NOTES (documented per EC-13):\n * - TOTP secrets are equivalent to passwords. Encrypt at rest using a\n * separate KMS key from the session secret. If your DB leaks, ALL\n * 2FA codes are compromised — rotate by forcing all users to re-enroll.\n * - Use `verifyTotp` constant-time (we use crypto.timingSafeEqual internally).\n */\n\n// CR-012 fix: use the shared constant-time helper that does not early-exit\n// on length mismatch (the previous local version did, leaking the digit\n// count via timing).\nimport { constantTimeEquals } from '../_internal/encoding.js'\n\n/** Supported HMAC algorithms per RFC 6238 §1.2. Default SHA-1. */\nexport type TotpAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512'\n\nexport interface TotpOptions {\n /** Shared secret — bytes (preferred) OR base32 string. */\n secret: Uint8Array | string\n /** Time step in seconds. Default 30 (RFC 6238 §5.2 recommended). */\n step?: number\n /** Code length: 6, 7, or 8 digits. Default 6. */\n digits?: 6 | 7 | 8\n /** HMAC algorithm. Default SHA-1 (RFC default). */\n algorithm?: TotpAlgorithm\n /** Time in ms since epoch. Default Date.now(). */\n time?: number\n}\n\nexport interface VerifyTotpOptions extends TotpOptions {\n /**\n * Drift tolerance in number of steps on each side. Default 1\n * (RFC 6238 §5.2 recommends ±1 step = 90s total window).\n */\n window?: number\n}\n\nconst DEFAULT_STEP = 30\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_ALGORITHM: TotpAlgorithm = 'SHA-1'\n\n/** Decode base32 (RFC 4648, no padding) to bytes. Throws on invalid input. */\nfunction base32Decode(s: string): Uint8Array {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n // Trailing `=` is bounded by spec to 0..6 chars; `\\s+` collapses runs\n // of whitespace. Input length is the user's TOTP secret (10..50 chars\n // typical), so super-linear backtracking cannot escalate.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded inputs (TOTP secrets are short)\n const clean = s.toUpperCase().replace(/=+$/, '').replace(/\\s+/g, '')\n if (!/^[A-Z2-7]+$/.test(clean)) {\n throw new Error('Invalid base32 secret')\n }\n const bits: number[] = []\n for (const c of clean) {\n const idx = alphabet.indexOf(c)\n for (let i = 4; i >= 0; i--) bits.push((idx >> i) & 1)\n }\n // Trim trailing partial byte\n const fullBytes = Math.floor(bits.length / 8)\n const out = new Uint8Array(fullBytes)\n for (let i = 0; i < fullBytes; i++) {\n let v = 0\n for (let j = 0; j < 8; j++) v = (v << 1) | bits[i * 8 + j]\n out[i] = v\n }\n return out\n}\n\n/** Encode bytes to base32 (RFC 4648, no padding). */\nfunction base32Encode(bytes: Uint8Array): string {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n let out = ''\n let buffer = 0\n let bits = 0\n for (const b of bytes) {\n buffer = (buffer << 8) | b\n bits += 8\n while (bits >= 5) {\n out += alphabet[(buffer >> (bits - 5)) & 0x1f]\n bits -= 5\n }\n }\n if (bits > 0) out += alphabet[(buffer << (5 - bits)) & 0x1f]\n return out\n}\n\nfunction normalizeSecret(secret: Uint8Array | string): Uint8Array {\n if (typeof secret === 'string') return base32Decode(secret)\n return secret\n}\n\n/** Counter (8-byte big-endian) from time in ms / step in seconds. */\nfunction counterBytes(timeMs: number, stepSec: number): Uint8Array {\n const counter = Math.floor(timeMs / 1000 / stepSec)\n const out = new Uint8Array(8)\n // Two 32-bit halves (Number can safely represent up to 2^53; OK until year 285,000+)\n const hi = Math.floor(counter / 0x100000000)\n const lo = counter >>> 0\n out[0] = (hi >>> 24) & 0xff\n out[1] = (hi >>> 16) & 0xff\n out[2] = (hi >>> 8) & 0xff\n out[3] = hi & 0xff\n out[4] = (lo >>> 24) & 0xff\n out[5] = (lo >>> 16) & 0xff\n out[6] = (lo >>> 8) & 0xff\n out[7] = lo & 0xff\n return out\n}\n\nasync function hmac(\n secret: Uint8Array,\n message: Uint8Array,\n algorithm: TotpAlgorithm,\n): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n secret as BufferSource,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign'],\n )\n const sig = await crypto.subtle.sign('HMAC', key, message as BufferSource)\n return new Uint8Array(sig)\n}\n\n/** RFC 4226 §5.3 dynamic truncation. */\nfunction truncate(mac: Uint8Array, digits: number): string {\n const offset = mac[mac.length - 1] & 0x0f\n const code =\n ((mac[offset] & 0x7f) << 24) |\n ((mac[offset + 1] & 0xff) << 16) |\n ((mac[offset + 2] & 0xff) << 8) |\n (mac[offset + 3] & 0xff)\n const mod = 10 ** digits\n return String(code % mod).padStart(digits, '0')\n}\n\n/** Generate a TOTP code for a given time. */\nexport async function generateTotp(opts: TotpOptions): Promise<string> {\n const secret = normalizeSecret(opts.secret)\n const step = opts.step ?? DEFAULT_STEP\n const digits = opts.digits ?? DEFAULT_DIGITS\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const mac = await hmac(secret, counterBytes(time, step), algorithm)\n return truncate(mac, digits)\n}\n\n/**\n * Verify a TOTP code against the current window ± `window` steps.\n *\n * Returns `false` for malformed tokens (non-digit, wrong length) without\n * throwing — defensive against accidental user-input passthrough.\n */\nexport async function verifyTotp(token: string, opts: VerifyTotpOptions): Promise<boolean> {\n if (typeof token !== 'string') return false\n const digits = opts.digits ?? DEFAULT_DIGITS\n if (!/^\\d+$/.test(token) || token.length !== digits) return false\n\n const step = opts.step ?? DEFAULT_STEP\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const drift = opts.window ?? 1\n const secret = normalizeSecret(opts.secret)\n\n // Walk the whole drift range constant-time-ish (no early exit).\n let matched = false\n for (let delta = -drift; delta <= drift; delta++) {\n const t = time + delta * step * 1000\n const code = truncate(await hmac(secret, counterBytes(t, step), algorithm), digits)\n if (constantTimeEquals(code, token)) matched = true\n }\n return matched\n}\n\n/** Generate a cryptographically random TOTP secret. Default 20 bytes (RFC minimum). */\nexport function generateTotpSecret(bytes = 20): Uint8Array {\n if (bytes < 16) {\n throw new Error('TOTP secret must be at least 16 bytes (RFC 6238 §3 minimum)')\n }\n const out = new Uint8Array(bytes)\n crypto.getRandomValues(out)\n return out\n}\n\n/**\n * Build an `otpauth://` URI for QR code enrollment. Format follows the\n * de facto Google Authenticator spec adopted by every major TOTP app.\n * otpauth://totp/<issuer>:<account>?secret=<base32>&issuer=<issuer>&algorithm=<alg>&digits=<n>&period=<sec>\n */\nexport interface TotpUriOptions {\n secret: Uint8Array\n issuer: string\n account: string\n algorithm?: TotpAlgorithm\n digits?: 6 | 7 | 8\n step?: number\n}\n\nexport function totpUri(opts: TotpUriOptions): string {\n const label = `${opts.issuer}:${opts.account}`\n const params = new URLSearchParams({\n secret: base32Encode(opts.secret),\n issuer: opts.issuer,\n algorithm: opts.algorithm ?? DEFAULT_ALGORITHM,\n digits: String(opts.digits ?? DEFAULT_DIGITS),\n period: String(opts.step ?? DEFAULT_STEP),\n })\n return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`\n}\n","/**\n * T6.3 — Backup codes for 2FA recovery.\n *\n * generateBackupCodes returns N plaintexts (display once to user) + hashes\n * (store in DB). verifyBackupCode constant-time-walks all hashes and\n * returns the matchedHash so caller can delete the used code from storage\n * — replay protection lives at the caller (we can't see their database).\n *\n * SECURITY NOTES:\n * - Codes have ~40 bits entropy (8 chars × 5 bits per char from a\n * 32-char alphabet excluding I/L/O/0/1). Single-use → argon2id\n * overhead is unnecessary; SHA-256 of the normalized code suffices.\n * - The caller MUST atomically delete the matched hash on successful\n * verify. Without that, the code is replayable. We surface\n * matchedHash explicitly so this step is conspicuous.\n * - Pair this with throttleLoginAttempts (T6.1) on the verify endpoint\n * to prevent online brute-force against the 40-bit space.\n */\n\nexport interface BackupCodeOptions {\n /** How many codes to generate. Default 10. */\n count?: number\n /** Length in chars (excluding separator). Default 8. */\n length?: number\n /** Separator inserted at the midpoint. Default '-'; pass null to omit. */\n separator?: '-' | null\n /** Custom alphabet. Default excludes ambiguous chars (I, L, O, 0, 1). */\n alphabet?: string\n}\n\nexport interface BackupCode {\n plaintext: string\n hash: string\n}\n\nconst DEFAULT_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // no I, O, 0, 1\nconst DEFAULT_COUNT = 10\nconst DEFAULT_LENGTH = 8\n\n/**\n * Generate cryptographically random backup codes + their SHA-256 hashes.\n *\n * Plaintext is uppercase alphanumeric (default alphabet excludes ambiguous\n * chars). Separator inserted at the midpoint (default `-`).\n */\nexport async function generateBackupCodes(opts: BackupCodeOptions = {}): Promise<BackupCode[]> {\n const count = opts.count ?? DEFAULT_COUNT\n const length = opts.length ?? DEFAULT_LENGTH\n const separator = opts.separator === undefined ? '-' : opts.separator\n const alphabet = opts.alphabet ?? DEFAULT_ALPHABET\n\n // CR-022 fix: bound count and length explicitly. Pathological inputs\n // (count=Infinity, length=0) would cause an infinite loop or wasteful\n // resource usage. Bounds are generous (count ≤ 1000, length ∈ [4, 64])\n // — well above any realistic 2FA backup-codes use case.\n if (!Number.isInteger(count) || count <= 0 || count > 1000) {\n throw new RangeError(\n `generateBackupCodes: count must be an integer in [1, 1000] (got ${String(count)})`,\n )\n }\n if (!Number.isInteger(length) || length < 4 || length > 64) {\n throw new RangeError(\n `generateBackupCodes: length must be an integer in [4, 64] (got ${String(length)})`,\n )\n }\n if (alphabet.length < 8) {\n throw new RangeError(\n `generateBackupCodes: alphabet must contain at least 8 distinct chars (got ${alphabet.length})`,\n )\n }\n\n const codes: BackupCode[] = []\n const seen = new Set<string>()\n while (codes.length < count) {\n const raw = randomFromAlphabet(length, alphabet)\n if (seen.has(raw)) continue\n seen.add(raw)\n const plaintext = formatWithSeparator(raw, separator)\n const hash = await sha256Hex(normalizeCode(plaintext))\n codes.push({ plaintext, hash })\n }\n return codes\n}\n\n/**\n * Verify a code against a list of stored hashes. Walks all hashes\n * constant-time (no early exit). Returns `valid:true` + matchedHash on\n * hit; `valid:false` otherwise.\n *\n * `matchedHash` is the EXACT string the caller stored, ready to be passed\n * to a `DELETE FROM backup_codes WHERE hash = ?` query.\n */\nexport async function verifyBackupCode(\n code: string,\n hashes: readonly string[],\n): Promise<{ valid: boolean; matchedHash?: string }> {\n const candidate = await sha256Hex(normalizeCode(code))\n let matchedHash: string | undefined\n // Constant-time iteration over hashes: never short-circuit.\n for (const h of hashes) {\n if (constantTimeEquals(h, candidate)) {\n matchedHash = h\n }\n }\n return matchedHash ? { valid: true, matchedHash } : { valid: false }\n}\n\n/**\n * Normalize a code for hashing: uppercase + strip separator. This way\n * `abcd-efgh` and `ABCDEFGH` produce the same hash. Whitespace is also\n * stripped defensively.\n */\nfunction normalizeCode(input: string): string {\n return input.toUpperCase().replace(/[-\\s]+/g, '')\n}\n\nfunction formatWithSeparator(raw: string, separator: '-' | null): string {\n if (!separator || raw.length < 4) return raw\n const mid = Math.floor(raw.length / 2)\n return raw.slice(0, mid) + separator + raw.slice(mid)\n}\n\nfunction randomFromAlphabet(length: number, alphabet: string): string {\n const random = new Uint8Array(length)\n crypto.getRandomValues(random)\n let out = ''\n for (let i = 0; i < length; i++) {\n out += alphabet[random[i] % alphabet.length]\n }\n return out\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { RateLimitStore } from '../rate-limit/rate-limit-store.js'\n\n/**\n * T6.1 — Login throttling primitive.\n *\n * Per-credential failure counter backed by any `RateLimitStore`. After\n * `maxAttempts` failures within `windowMs`, the identifier is locked\n * for `lockoutMs`. Successful login resets the counter.\n *\n * Usage in a login handler:\n *\n * const state = await checkThrottle({ store, identifier: hash(email) })\n * if (!state.allowed) return 429\n * const valid = await verifyPassword(creds)\n * await recordAttempt({ store, identifier: hash(email) }, valid)\n * if (!valid) return 401\n *\n * SECURITY:\n * - NEVER pass raw email/username as `identifier`. Hash or normalize\n * first (e.g. SHA-256 of the lowercased email). This prevents PII\n * from landing in the rate-limit store or audit logs.\n * - Combine with the per-route IP rate limit (T2.2) for layered defense:\n * IP-level limits stop scripted floods; credential-level limits stop\n * distributed brute force.\n */\n\nexport interface ThrottleOptions {\n store: RateLimitStore\n /**\n * Stable identifier per credential. Hash raw emails/usernames before\n * passing in — never leak PII to the rate-limit key space.\n */\n identifier: string\n /** Failures before lockout. Default 5. */\n maxAttempts?: number\n /** Sliding window for counting failures. Default 15 minutes. */\n windowMs?: number\n /** Lockout duration after maxAttempts hit. Default 1 hour. */\n lockoutMs?: number\n}\n\nexport interface ThrottleState {\n allowed: boolean\n remainingAttempts: number\n /** Set when `allowed === false`. Absolute timestamp when the lockout expires. */\n lockedUntil?: Date\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 5\nconst DEFAULT_LOCKOUT_MS = 60 * 60_000\n\n/**\n * Read the current throttle state for an identifier WITHOUT modifying\n * the counter. Call before validating credentials so locked attempts\n * are rejected fast.\n */\nexport async function checkThrottle(opts: ThrottleOptions): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n const state = await opts.store.get(opts.identifier)\n if (!state) {\n return { allowed: true, remainingAttempts: maxAttempts }\n }\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n // Lockout expires when the store TTL fires.\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n\n/**\n * Record an attempt outcome. On success → reset the counter. On failure\n * → increment within the sliding window. Returns the new throttle state.\n *\n * Storage model: the store entry's TTL is `lockoutMs` (which doubles as\n * the failure-accumulation window). When `lockoutMs` is short — common\n * in tests or for soft lockouts — the entry auto-expires and the next\n * attempt starts fresh.\n */\nexport async function recordAttempt(\n opts: ThrottleOptions,\n success: boolean,\n): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n if (success) {\n await opts.store.reset(opts.identifier)\n return {\n allowed: true,\n remainingAttempts: maxAttempts,\n }\n }\n const lockoutMs = opts.lockoutMs ?? DEFAULT_LOCKOUT_MS\n const state = await opts.store.incr(opts.identifier, lockoutMs)\n\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n","/**\n * T7.3 — RFC 7636 PKCE primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc7636\n *\n * Pure-function, dependency-free implementation using Web Crypto.\n *\n * Usage in an OAuth authorization-code flow:\n *\n * const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkceChallenge()\n * // Store codeVerifier in the user's session\n * // Send codeChallenge + codeChallengeMethod in the /authorize redirect\n * // ... user authenticates with provider ...\n * // On /callback, send codeVerifier with the code → token exchange\n */\n\nexport interface PkceChallenge {\n codeVerifier: string\n codeChallenge: string\n codeChallengeMethod: 'S256'\n}\n\n// CR-020 DRY: single canonical base64url encoder lives in _internal/encoding.\nimport { base64urlEncode } from '../_internal/encoding.js'\n\n/**\n * Compute the code_challenge from a code_verifier per RFC 7636 §4.2.\n * Exported separately to enable RFC 7636 Appendix B vector testing.\n */\nexport async function pkceChallengeFromVerifier(verifier: string): Promise<string> {\n const bytes = new TextEncoder().encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return base64urlEncode(new Uint8Array(hash))\n}\n\n/**\n * Generate a fresh PKCE challenge pair.\n *\n * Verifier: 32 cryptographically random bytes → 43-char base64url string.\n * RFC 7636 §4.1 allows 43–128 chars; we ship the minimum-safe default.\n *\n * Method: 'S256' only — no 'plain' fallback. RFC 7636 §7.2 strongly\n * discourages plain and modern providers reject it.\n */\nexport async function generatePkceChallenge(): Promise<PkceChallenge> {\n const random = new Uint8Array(32)\n crypto.getRandomValues(random)\n const codeVerifier = base64urlEncode(random)\n const codeChallenge = await pkceChallengeFromVerifier(codeVerifier)\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }\n}\n","/**\n * T7.4 — OAuth `state` parameter helpers (RFC 6749 §10.12).\n *\n * `state` is a cryptographically random anti-CSRF token. The client\n * stores it (in session/cookie/db) before redirecting to the\n * authorization endpoint and verifies it on the callback. Without it,\n * an attacker can authorize on the user's account using a captured code.\n */\n\n// CR-020 DRY + CR-012 constant-time: shared helpers in _internal/encoding.\nimport { base64urlEncode, constantTimeEquals } from '../_internal/encoding.js'\n\n/**\n * Generate a cryptographically random state token. Default 32 bytes\n * (~43 base64url chars). Increase for higher entropy if your provider\n * supports it.\n */\nexport function generateOAuthState(opts: { bytes?: number } = {}): string {\n const len = opts.bytes ?? 32\n const buf = new Uint8Array(len)\n crypto.getRandomValues(buf)\n return base64urlEncode(buf)\n}\n\n/**\n * Verify a state token. Constant-time compare. Empty inputs always fail\n * (defensive — never accept empty as \"they match\").\n *\n * CR-012 fix: `constantTimeEquals` does not early-exit on length mismatch,\n * so the comparison time is independent of how the input was forged.\n */\nexport function verifyOAuthState(provided: string, stored: string): boolean {\n return constantTimeEquals(provided, stored)\n}\n","/**\n * T7.4 — OpenID Connect Discovery 1.0 metadata fetcher.\n *\n * Reference: https://openid.net/specs/openid-connect-discovery-1_0.html\n *\n * Caches metadata in module scope. Cache key is the exact issuer string\n * (trailing-slash sensitive per RFC 8414 §3). Failures are NOT cached —\n * subsequent calls retry the fetch.\n *\n * EC-7 — HTTPS enforced. RFC 8414 §3 requires HTTPS for OIDC issuers.\n * Localhost (loopback) is allowed for development.\n */\n\nexport interface OidcMetadata {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n jwks_uri?: string\n userinfo_endpoint?: string\n end_session_endpoint?: string\n scopes_supported?: string[]\n response_types_supported?: string[]\n grant_types_supported?: string[]\n id_token_signing_alg_values_supported?: string[]\n [key: string]: unknown // OIDC providers ship many optional fields\n}\n\nconst cache = new Map<string, Promise<OidcMetadata>>()\n\n/** Clear the discovery cache. Used by tests. */\nexport function clearOidcCache(): void {\n cache.clear()\n}\n\nfunction isLoopback(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1'\n}\n\n/**\n * Fetch (or return cached) OIDC provider metadata.\n *\n * Throws when:\n * - Issuer URL is malformed\n * - Issuer is HTTP and not a loopback host (EC-7)\n * - HTTP response is non-OK\n * - Metadata lacks `authorization_endpoint` (sanity check)\n */\nexport async function discoverOidcProvider(issuer: string | URL): Promise<OidcMetadata> {\n const issuerStr = typeof issuer === 'string' ? issuer : issuer.toString()\n // Validate protocol BEFORE checking cache — invalid issuers should never poison.\n const url = new URL(issuerStr)\n if (url.protocol !== 'https:' && !isLoopback(url.hostname)) {\n throw new Error(\n `OIDC issuer must use HTTPS (received \"${url.protocol}//${url.hostname}…\"). ` +\n `RFC 8414 §3 forbids plain HTTP except for loopback hosts.`,\n )\n }\n\n const cached = cache.get(issuerStr)\n if (cached) return cached\n\n const promise = (async () => {\n const wellKnown = new URL(\n '.well-known/openid-configuration',\n issuerStr.endsWith('/') ? issuerStr : issuerStr + '/',\n )\n const res = await fetch(wellKnown)\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status} ${res.statusText} at ${wellKnown}`)\n }\n const metadata = (await res.json()) as OidcMetadata\n if (!metadata.authorization_endpoint) {\n throw new Error('OIDC metadata missing authorization_endpoint — provider response is invalid')\n }\n return metadata\n })()\n // Cache the promise; on rejection, remove so the next call retries.\n cache.set(issuerStr, promise)\n promise.catch(() => {\n if (cache.get(issuerStr) === promise) cache.delete(issuerStr)\n })\n return promise\n}\n"],"mappings":"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","names":["constantTimeEquals"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/security/csrf.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\n\nimport type { AuditLogger } from '../observability/audit-log.js'\nimport { safeAudit } from '../observability/audit-log.js'\n\n/**\n * CSRF enforcement mode.\n *\n * - `off` — skip CSRF entirely. Use only when you have another defense\n * (e.g. you don't ship session cookies, all auth is bearer).\n * - `warn` — log a structured warning when the check would fail, but\n * still serve the request. Default for 0.2.0. Migration mode.\n * - `strict` — reject failing requests with 403 + code `CSRF_INVALID`.\n * Will become the default in 0.3.0.\n */\nexport type CsrfMode = 'off' | 'warn' | 'strict'\n\n/**\n * Per-request structured logger surface. Only `warn` is used by enforceCsrf;\n * we don't require a full Logger here so callers can pass a mock or the\n * console directly.\n */\nexport interface CsrfLogger {\n warn: (payload: CsrfWarnPayload) => void\n /** Optional path the request was destined for — used for log correlation. */\n path?: string\n}\n\n/**\n * T5.1 — Rails-inspired per-route escalation.\n *\n * `routes` accepts string (exact match) or RegExp entries. When a request\n * path matches AND the request would otherwise emit a warning, the\n * `behavior` field decides what happens:\n *\n * - `'warn'` → normal warn dispatch (no-op vs default)\n * - `'raise'` → escalate to 403 regardless of global `csrf` mode\n *\n * `'raise'` never downgrades: when global mode is `'off'`, validation is\n * skipped entirely and disallowed dispatch never runs.\n */\nexport interface DisallowedConfig {\n routes: (string | RegExp)[]\n behavior: 'warn' | 'raise'\n}\n\n/**\n * Test whether `path` matches any of the supplied patterns. String\n * patterns are EXACT (trailing slash matters — use RegExp for tolerance).\n *\n * EC-5: when a RegExp carries the `/g` flag, `.test()` mutates\n * `lastIndex` and the next invocation may miss. We reset `lastIndex`\n * before each test so the matcher is a pure function.\n */\nexport function matchDisallowed(path: string, patterns: readonly (string | RegExp)[]): boolean {\n for (const p of patterns) {\n if (typeof p === 'string') {\n if (path === p) return true\n } else if (p instanceof RegExp) {\n p.lastIndex = 0\n if (p.test(path)) return true\n }\n // Neither string nor RegExp: ignore silently (defensive — the public\n // type forbids it but runtime data may slip past).\n }\n return false\n}\n\n/**\n * T2.2 — Stable cutover identifier shipped with every csrf.warn payload.\n *\n * Convention borrowed from Vite's `deprecations.ts:74` — a `code` plus a\n * `docsUrl` lets users (a) grep their logs for a single stable identifier\n * to find every csrf.warn line, and (b) click through directly to the\n * migration guide. Strings are exported constants so the analyzer (T2.3)\n * and migration guide can reference the same source of truth.\n */\nexport const CSRF_WARN_CODE = 'CSRF_STRICT_CUTOVER' as const\nexport const CSRF_WARN_DOCS_URL = 'https://theokit.dev/upgrade/csrf-strict-cutover' as const\n\n/**\n * Pick a header value, choosing the first entry when an array (Node sets\n * arrays for headers that legitimately appear multiple times). Returns\n * `''` when the header is absent or empty — callers treat `''` as \"skip\".\n */\nfunction pickHeader(value: string | string[] | undefined): string {\n if (typeof value === 'string') return value\n if (Array.isArray(value) && value.length > 0) return value[0]\n return ''\n}\n\nexport interface CsrfWarnPayload {\n event: 'csrf.warn'\n method: string\n path: string | undefined\n reason: string\n /**\n * Stable identifier for the 0.2 → 0.3 CSRF strict cutover. Always\n * `'CSRF_STRICT_CUTOVER'`. Grep-able from prod logs.\n */\n code: string\n /**\n * Link to the section of the migration guide explaining how to clear\n * this specific warning class.\n */\n docsUrl: string\n}\n\n/**\n * T5a.2 Phase B (slice 1/6): pure header-only CSRF check extracted from\n * `validateCsrf(req: IncomingMessage)` so it can be re-used by the Web-\n * Standards `validateCsrfRequest(request: Request)` sibling. Per the T5a.2\n * plan v1.0 § Phase B, header-only leaves are first to migrate. This is\n * the dual-signature pattern (anti-pattern #2 avoidance): IncomingMessage\n * consumers unchanged; new Request consumers go through the same logic\n * via the shared helper.\n *\n * Pure logic — accepts pre-extracted header values as strings or null.\n */\nfunction isCsrfValidFromHeaders(opts: {\n csrfActionHeader: string | null\n origin: string | null\n host: string | null\n}): { valid: true } | { valid: false; reason: string } {\n // 1. Custom header must be present (primary defense — simple form posts\n // cannot set custom headers, browsers gate via CORS preflight)\n if (opts.csrfActionHeader !== '1') {\n return { valid: false, reason: 'Missing X-Theo-Action header' }\n }\n\n // 2. Origin matching (secondary defense)\n if (opts.origin === null || opts.origin === '') {\n // Browsers omit Origin for same-origin requests — treat as valid\n return { valid: true }\n }\n\n if (opts.host === null || opts.host === '') {\n return { valid: true }\n }\n\n try {\n const originHost = new URL(opts.origin).host\n if (originHost !== opts.host) {\n return { valid: false, reason: `Origin ${opts.origin} does not match host ${opts.host}` }\n }\n } catch {\n return { valid: false, reason: `Invalid origin: ${opts.origin}` }\n }\n\n return { valid: true }\n}\n\nexport function validateCsrf(\n req: IncomingMessage,\n): { valid: true } | { valid: false; reason: string } {\n // IncomingMessage adapter — normalize Node header shape to the pure\n // helper's input shape (string|null).\n const action = req.headers['x-theo-action']\n const origin = req.headers.origin\n const host = req.headers.host\n return isCsrfValidFromHeaders({\n csrfActionHeader: typeof action === 'string' ? action : null,\n origin: origin !== undefined ? pickHeader(origin) || null : null,\n host: host !== undefined ? pickHeader(host) || null : null,\n })\n}\n\n/**\n * T5a.2 Phase B (slice 1/6) — Web-Standards-shaped CSRF validator.\n *\n * Mirror of `validateCsrf(req: IncomingMessage)` for the Web `Request`\n * shape. Consumes `request.headers.get(name)` (native Web `Headers` API)\n * instead of `req.headers[name]` (Node `IncomingMessage` indexer). Same\n * CSRF policy + same return shape — the difference is only the input\n * extraction.\n *\n * Used by `executeWebRequest` (T5a.2 Phase A) to enforce CSRF on the\n * Web-Standards request handler entry-point.\n */\nexport function validateCsrfRequest(\n request: Request,\n): { valid: true } | { valid: false; reason: string } {\n return isCsrfValidFromHeaders({\n csrfActionHeader: request.headers.get('x-theo-action'),\n origin: request.headers.get('origin'),\n host: request.headers.get('host'),\n })\n}\n\n/**\n * Enforce CSRF policy with mode-aware behavior. Wrapper over `validateCsrf`\n * that turns the boolean valid/invalid into a request-level allow decision,\n * gated by mode + structured warning in warn mode.\n *\n * Phase 5 — CSRF warn-first (EC-1). See plan docs/plans/nextjs-maturity-plan.md.\n */\n/**\n * Dispatch the csrf.warn payload to both the structured logger and the\n * audit sink (when configured). Extracted from `enforceCsrf` to keep that\n * function's complexity within ceiling.\n */\nfunction dispatchCsrfWarn(\n req: IncomingMessage,\n reason: string,\n logger: CsrfLogger | undefined,\n auditLogger: AuditLogger | undefined,\n pathFallback = '',\n): void {\n const payload: CsrfWarnPayload = {\n event: 'csrf.warn',\n method: req.method ?? 'UNKNOWN',\n path: logger?.path ?? pathFallback,\n reason,\n code: CSRF_WARN_CODE,\n docsUrl: CSRF_WARN_DOCS_URL,\n }\n logger?.warn(payload)\n // `metadata` is typed as Record<string, unknown>; CsrfWarnPayload is a\n // structurally-equivalent shape but lacks the index signature.\n safeAudit(auditLogger, {\n action: 'csrf.warn',\n actor: { type: 'anonymous' },\n metadata: { ...payload },\n })\n}\n\nexport function enforceCsrf(\n req: IncomingMessage,\n mode: CsrfMode,\n logger?: CsrfLogger,\n disallowed?: DisallowedConfig,\n auditLogger?: AuditLogger,\n): { allow: boolean; reason?: string } {\n if (mode === 'off') {\n // `off` short-circuits before disallowed dispatch — users who set\n // csrf: 'off' globally have explicitly turned validation off, and\n // disallowed must never re-introduce it. The escape hatch is to\n // set csrf: 'warn' and use disallowed for surgical strict pockets.\n return { allow: true }\n }\n\n const check = validateCsrf(req)\n if (check.valid) {\n return { allow: true }\n }\n\n // T5.1 — disallowed dispatch: when the failing request matches a\n // disallowed pattern AND behavior is 'raise', escalate to 403 even if\n // global mode is 'warn'. Strict mode would 403 anyway, so the branch\n // is a no-op there.\n if (disallowed?.behavior === 'raise') {\n const path = logger?.path ?? req.url ?? ''\n if (matchDisallowed(path, disallowed.routes)) {\n return { allow: false, reason: check.reason }\n }\n }\n\n if (mode === 'warn') {\n // T2.1: emit via warnOnce by default — callers can override via the\n // injected logger.warn (tests, custom log routers).\n // T2.2: include the stable cutover code + docsUrl so logs are\n // grep-able and click-through-able.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: true, reason: check.reason }\n }\n\n // strict — 403 the request, AND emit a warn payload so the dev (and\n // devtools UI) sees WHY it was blocked + the docsUrl to fix it.\n // Without this, strict-mode users get a silent 403 with no context.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: false, reason: check.reason }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/observability/audit-log.ts"],"sourcesContent":["/**\n * T4.1 — Audit logging interface + default JSON stdout sink.\n *\n * Per ADR D4: define the interface; ship a zero-dep default; reserve\n * adapter shapes for Postgres, File, OpenTelemetry, Sentry as follow-up\n * packages. Persistence has heavy deps (`pg`, `better-sqlite3`); we\n * keep core dep-free and let users opt in.\n *\n * Compatibility:\n * - Node / Bun / Deno / Vercel — console.log is sync, captured.\n * - Edge runtimes (CF Workers, Vercel Edge) — console.log is captured\n * but may be rate-limited by the platform. For high-volume edge audit,\n * implement a custom sink writing to a queue / HTTP endpoint.\n */\n\nexport interface AuditEvent {\n /** Domain-qualified verb. Convention: `<domain>.<verb>` (e.g. csrf.warn, session.rotated). */\n action: string\n /** Who triggered the event. Anonymous = no auth at time of event. */\n actor?: { type: 'user' | 'system' | 'anonymous'; id?: string }\n /** What was operated on (optional). */\n resource?: { type: string; id?: string }\n /** Arbitrary event-specific metadata. JSON-serializable. */\n metadata?: Record<string, unknown>\n /** ISO 8601 timestamp. If absent, sink fills in `new Date().toISOString()`. */\n timestamp?: string\n /** Optional trace id (populated by middleware from `x-trace-id`). */\n traceId?: string\n}\n\nexport interface AuditLogger {\n log(event: AuditEvent): void | Promise<void>\n}\n\n/**\n * Default sink: one JSON line per event to stdout. Sync. Never throws.\n *\n * EC: circular refs / BigInt values fall back to a placeholder line so\n * the event is still observable (action + traceId) without crashing the\n * request lifecycle.\n */\nexport class JsonStdoutSink implements AuditLogger {\n log(event: AuditEvent): void {\n const enriched = {\n level: 'audit' as const,\n ...event,\n timestamp: event.timestamp ?? new Date().toISOString(),\n }\n try {\n // eslint-disable-next-line no-console -- JsonStdoutSink IS the audit output\n console.log(JSON.stringify(enriched, jsonReplacer))\n } catch {\n // eslint-disable-next-line no-console -- fallback when payload won't serialize\n console.log(\n `{\"level\":\"audit\",\"action\":${JSON.stringify(event.action)},\"timestamp\":${JSON.stringify(enriched.timestamp)},\"note\":\"payload could not be serialized\"}`,\n )\n }\n }\n}\n\n/**\n * Replacer that walks BigInt → string. Circular ref handling is via the\n * outer try/catch (JSON.stringify throws TypeError on cycles; we drop to\n * the fallback line). We don't implement custom cycle-breaking walker\n * because the audit payload is meant to be JSON — if user metadata has\n * a cycle, the right answer is to fix the caller, not silently lose\n * the structure.\n */\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') return value.toString()\n return value\n}\n\n/**\n * No-op logger. Returned when `config.audit` is unset. Zero overhead;\n * framework wiring sites null-check before calling.\n */\nexport function createNoOpLogger(): AuditLogger {\n return {\n log() {\n // intentionally empty\n },\n }\n}\n\n/**\n * T4.2 — Safe-emit wrapper. Used by framework wiring sites (csrf.ts,\n * rate-limit.ts, session.ts) so a logger throw NEVER propagates into\n * the request handler.\n */\nexport function safeAudit(logger: AuditLogger | undefined, event: AuditEvent): void {\n if (!logger) return\n try {\n const r = logger.log(event)\n // Discard the Promise — async sinks are fire-and-forget by design.\n if (r && typeof r.then === 'function') {\n r.catch(() => {\n // swallow async sink failures — audit must never crash the request\n })\n }\n } catch {\n // swallow sync sink failures — audit must never crash the request\n }\n}\n"],"mappings":";AAyCO,IAAM,iBAAN,MAA4C;AAAA,EACjD,IAAI,OAAyB;AAC3B,UAAM,WAAW;AAAA,MACf,OAAO;AAAA,MACP,GAAG;AAAA,MACH,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACvD;AACA,QAAI;AAEF,cAAQ,IAAI,KAAK,UAAU,UAAU,YAAY,CAAC;AAAA,IACpD,QAAQ;AAEN,cAAQ;AAAA,QACN,6BAA6B,KAAK,UAAU,MAAM,MAAM,CAAC,gBAAgB,KAAK,UAAU,SAAS,SAAS,CAAC;AAAA,MAC7G;AAAA,IACF;AAAA,EACF;AACF;AAUA,SAAS,aAAa,MAAc,OAAyB;AAC3D,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS;AACrD,SAAO;AACT;AAMO,SAAS,mBAAgC;AAC9C,SAAO;AAAA,IACL,MAAM;AAAA,IAEN;AAAA,EACF;AACF;AAOO,SAAS,UAAU,QAAiC,OAAyB;AAClF,MAAI,CAAC,OAAQ;AACb,MAAI;AACF,UAAM,IAAI,OAAO,IAAI,KAAK;AAE1B,QAAI,KAAK,OAAO,EAAE,SAAS,YAAY;AACrC,QAAE,MAAM,MAAM;AAAA,MAEd,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/webhook/timing-safe-equal.ts","../src/server/webhook/raw-body.ts","../src/server/webhook/define-webhook.ts"],"sourcesContent":["/**\n * Constant-time byte comparison.\n *\n * Wraps `node:crypto.timingSafeEqual` when available (Node 6.6+) and falls\n * back to a constant-time XOR loop for runtimes without `node:crypto`\n * (Cloudflare Workers, Deno, Bun edge). Required to defeat timing attacks\n * on webhook signature verification — a naive `===` or `Buffer.equals`\n * would short-circuit on first mismatch byte and leak signature position\n * via wall-clock timing.\n *\n * Web Crypto's `crypto.subtle.verify` would also work, but requires a\n * `CryptoKey` object and is asymmetric (verify a signature against a key).\n * For our case — comparing two pre-computed digests — raw byte equality\n * is the correct primitive.\n *\n * @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b\n * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify\n */\n\ntype NodeTimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean\n\nlet cachedNodeImpl: NodeTimingSafeEqual | null | undefined\n\nfunction resolveNodeImpl(): NodeTimingSafeEqual | null {\n if (cachedNodeImpl !== undefined) return cachedNodeImpl\n try {\n // require() avoids the dynamic-import-of-node-builtin warnings some\n // bundlers emit; `node:crypto` is always synchronously available.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const cryptoMod = require('node:crypto') as {\n timingSafeEqual?: NodeTimingSafeEqual\n }\n cachedNodeImpl = cryptoMod.timingSafeEqual ?? null\n } catch {\n cachedNodeImpl = null\n }\n return cachedNodeImpl\n}\n\n/**\n * Returns true iff `a` and `b` have identical bytes. Takes time\n * proportional to `a.length` regardless of where bytes differ.\n *\n * @throws TypeError if either argument is not a Uint8Array.\n */\nexport function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) {\n throw new TypeError(\n `timingSafeEqual expects Uint8Array arguments (got ${typeof a} and ${typeof b})`,\n )\n }\n // Length mismatch is constant-time-safe by construction (no per-byte work).\n if (a.length !== b.length) return false\n if (a.length === 0) return true\n\n // Prefer the Node built-in when present — it's implemented in C and\n // matches the contract exactly. node:crypto.timingSafeEqual throws on\n // length mismatch, which we've already filtered above.\n const nodeImpl = resolveNodeImpl()\n if (nodeImpl) return nodeImpl(a, b)\n\n // Fallback: XOR-accumulate every byte into one accumulator. NEVER\n // short-circuit. The compiler / V8 will not constant-fold this loop\n // because the inputs are dynamic.\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i]\n }\n return diff === 0\n}\n","/**\n * Read a Web `Request` body as a raw string EXACTLY ONCE and expose it\n * alongside the original request (which remains readable by downstream\n * code). Enforces a configurable `maxBodyBytes` cap to prevent OOM via\n * pathological POST.\n *\n * MUST be called FIRST in the webhook pipeline, before any other code\n * touches the body (`request.text()`, `request.json()`, parsers). Once\n * the body is consumed, `Request.clone()` throws and this function fails.\n *\n * EC-101: default `maxBodyBytes = 1_000_000` (1 MB) covers Stripe\n * (256 KB max), Slack (4 MB but compressed), and is well below Node\n * memory thresholds. GitHub webhooks up to 25 MB MUST opt in\n * (`maxBodyBytes: 25_000_000`).\n *\n * @see https://docs.stripe.com/webhooks/signatures\n * @see https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks\n */\n\nexport const DEFAULT_MAX_BODY_BYTES = 1_000_000\n\nexport interface ReadRawBodyOptions {\n /** Maximum bytes to read before throwing `BodyTooLargeError`. Defaults to 1 MB. */\n maxBodyBytes?: number\n}\n\nexport interface RawBodyResult {\n /** UTF-8 decoded body (or empty string when no body present). */\n rawBody: string\n /** The ORIGINAL request, still readable by downstream code. */\n bodyClone: Request\n}\n\n/**\n * Thrown when the request body exceeds `maxBodyBytes`. Carries status\n * 413 (Payload Too Large) and stable code `BODY_TOO_LARGE` so callers\n * can map it to an HTTP response without sniffing the message.\n */\nexport class BodyTooLargeError extends Error {\n readonly status = 413\n readonly code = 'BODY_TOO_LARGE'\n\n constructor(\n public readonly limit: number,\n public readonly actualSeen: number,\n ) {\n super(`Request body exceeds maxBodyBytes: seen ${actualSeen} bytes, limit ${limit} bytes`)\n this.name = 'BodyTooLargeError'\n }\n}\n\nexport async function readRawBody(\n request: Request,\n options: ReadRawBodyOptions = {},\n): Promise<RawBodyResult> {\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n\n // Clone the request so the original remains readable. `.clone()` throws\n // synchronously (TypeError) if the body was already consumed — we let\n // that propagate so callers fail fast with a clear error.\n const clone = request.clone()\n\n // No body at all (GET, HEAD, or empty POST). Both clone.body and\n // clone.text() handle this gracefully but we short-circuit for clarity\n // and to avoid an unnecessary stream read.\n if (clone.body === null) {\n return { rawBody: '', bodyClone: request }\n }\n\n const reader = clone.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n\n try {\n for (;;) {\n const { value, done } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > maxBodyBytes) {\n // Cancel the stream without awaiting (avoids deadlock on some\n // ReadableStream impls). The finally block releases the lock.\n void reader.cancel()\n throw new BodyTooLargeError(maxBodyBytes, total)\n }\n chunks.push(value)\n }\n } finally {\n try {\n reader.releaseLock()\n } catch {\n // lock may already be released by cancel()\n }\n }\n\n // Concat all chunks into a single Uint8Array, then UTF-8 decode.\n const bytes = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n const rawBody = new TextDecoder().decode(bytes)\n\n return { rawBody, bodyClone: request }\n}\n","import {\n extractTraceContext,\n generateNewTraceContext,\n} from '../observability/trace-context-propagation.js'\n\nimport { BodyTooLargeError, readRawBody } from './raw-body.js'\nimport type {\n DefineWebhookOptions,\n VerifyResult,\n WebhookContext,\n WebhookDefinition,\n} from './webhook-types.js'\n\n/**\n * Declare a webhook handler with first-class signature verification\n * (R0.5.10, ADR-0005). Pure identity helper — returns the definition\n * unchanged for downstream dispatch.\n *\n * @example\n * ```ts\n * // server/webhooks/stripe.ts\n * import { defineWebhook } from 'theokit/server'\n * import { stripe } from 'theokit/server/webhook/providers'\n *\n * export default defineWebhook({\n * verify: stripe({ secret: process.env.STRIPE_WEBHOOK_SECRET! }),\n * async handler({ rawBody }) {\n * const event = JSON.parse(rawBody)\n * // ...\n * return new Response('ok')\n * },\n * })\n * ```\n */\nexport function defineWebhook(opts: DefineWebhookOptions): WebhookDefinition {\n return {\n verify: opts.verify,\n handler: opts.handler,\n maxBodyBytes: opts.maxBodyBytes,\n __theokit_kind: 'webhook',\n }\n}\n\n/**\n * Dispatch a webhook request through the definition's verify → handler\n * pipeline. Returns the handler's `Response` (or a wrapped one), or a\n * 401/413 response when verification or body size guards trip.\n *\n * EC-101: enforces `maxBodyBytes` (default 1MB via readRawBody).\n * EC-103: every throw from `verify` (sync or async) is treated as\n * `{ok: false, reason: 'verify threw: <message>'}`. Handler NEVER\n * invoked on verify failure.\n */\nexport async function dispatchWebhook(def: WebhookDefinition, request: Request): Promise<Response> {\n let rawBody: string\n let bodyClone: Request\n try {\n const result = await readRawBody(request, { maxBodyBytes: def.maxBodyBytes })\n rawBody = result.rawBody\n bodyClone = result.bodyClone\n } catch (err) {\n if (err instanceof BodyTooLargeError) {\n return new Response(JSON.stringify({ error: err.code, message: err.message }), {\n status: 413,\n headers: { 'content-type': 'application/json' },\n })\n }\n return new Response(\n JSON.stringify({\n error: 'BAD_REQUEST',\n message: err instanceof Error ? err.message : 'failed to read body',\n }),\n { status: 400, headers: { 'content-type': 'application/json' } },\n )\n }\n\n // Resolve traceId — propagate or generate.\n const incomingTrace = extractTraceContext(request.headers)\n const traceId = incomingTrace?.trace_id ?? generateNewTraceContext().trace_id\n\n // EC-103: verify exception MUST close the door, never let handler run.\n let verifyResult: VerifyResult\n try {\n verifyResult = await def.verify(bodyClone)\n } catch (err) {\n verifyResult = {\n ok: false,\n reason: `verify threw: ${err instanceof Error ? err.message : String(err)}`,\n }\n }\n\n if (!verifyResult.ok) {\n return new Response(\n JSON.stringify({ error: 'WEBHOOK_VERIFY_FAILED', message: verifyResult.reason }),\n { status: 401, headers: { 'content-type': 'application/json' } },\n )\n }\n\n const ctx: WebhookContext = {\n request: bodyClone,\n rawBody,\n traceId,\n signal: request.signal,\n }\n\n const result = await def.handler(ctx)\n if (result instanceof Response) return result\n if (result === undefined || result === null) {\n return new Response(null, { status: 204 })\n }\n return new Response(JSON.stringify(result), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n"],"mappings":"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","names":["result"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/contracts/theo-error.ts","../src/core/contracts/server-error-to-envelope.ts"],"sourcesContent":["/**\n * `TheoError` — envelope-emitting `Error` subclass per plan\n * g5-error-envelope-cross-layer v1.0 § Phase 1 / T1.2.\n *\n * Mirrors trpc's TRPCError ergonomic pattern (referencia:\n * trpc/packages/server/src/unstable-core-do-not-import/error/TRPCError.ts:65-87)\n * adapted for theokit's hybrid envelope model (blueprint Form 4).\n *\n * Per ADR D5: `meta.stack` is auto-stripped in non-dev builds when serialized\n * via `toJSON()` — defense against accidental stack leak to clients (encore\n * `json:\"-\"` analog).\n *\n * Lives in `core/contracts/` per architecture.md v3 invariant #3.\n */\n\nimport type { TheoErrorCode, TheoErrorEnvelope } from './error-envelope.js'\n\n/**\n * Constructor options. `code` and `message` are required; `cause` follows TC39\n * proposal-error-cause; `meta` and `ext` are opt-in per blueprint ADR D2.\n */\nexport interface TheoErrorOptions<TExt = unknown> {\n readonly code: TheoErrorCode\n readonly message: string\n readonly cause?: unknown\n readonly meta?: Record<string, unknown>\n readonly ext?: TExt\n}\n\n/**\n * `TheoError` — envelope-emitting Error subclass. Use this anywhere in\n * `theokit/server`, route handlers, or middleware to emit a typed,\n * cross-layer-compatible error.\n *\n * Mirrors TRPCError pattern: `code` is required, `message` defaults to\n * code if omitted, `cause` chain is preserved via TC39 proposal-error-cause.\n */\nexport class TheoError<TExt = unknown> extends Error {\n override readonly name = 'TheoError'\n readonly code: TheoErrorCode\n readonly meta?: Record<string, unknown>\n readonly ext?: TExt\n\n constructor(opts: TheoErrorOptions<TExt>) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined)\n this.code = opts.code\n this.meta = opts.meta\n this.ext = opts.ext\n }\n\n /**\n * The canonical envelope view of this error. Use when crossing the wire\n * boundary or feeding telemetry — exposes the shape consumers can rely on\n * without depending on the class identity.\n */\n get envelope(): TheoErrorEnvelope<TExt> {\n return {\n code: this.code,\n message: this.message,\n cause: (this as Error & { cause?: unknown }).cause,\n meta: this.meta,\n ext: this.ext,\n }\n }\n\n /**\n * JSON serialization — emits envelope shape only (no name/stack class\n * internals). Per ADR D5, `meta.stack` is auto-stripped in non-dev to\n * prevent accidental leak; other meta keys are preserved.\n */\n toJSON(): TheoErrorEnvelope<TExt> {\n const meta = this.meta ? sanitizeMeta(this.meta) : undefined\n return {\n code: this.code,\n message: this.message,\n cause: (this as Error & { cause?: unknown }).cause,\n meta,\n ext: this.ext,\n }\n }\n}\n\n/**\n * Strip server-only sensitive meta keys in non-dev builds. Per ADR D5,\n * `stack` is the canonical leak vector (trpc accidentally exposes it via\n * `config.isDev` even today — references/trpc getErrorShape.ts:29-31).\n * Default to safe-by-default; preserve in dev only.\n */\nfunction sanitizeMeta(meta: Record<string, unknown>): Record<string, unknown> | undefined {\n // Treat anything other than NODE_ENV === 'development' as production-safe\n // (CI, staging, production all strip). Matches the security posture in\n // blueprint EC-5 (default STRICT).\n if (process.env.NODE_ENV === 'development') {\n return meta\n }\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(meta)) {\n if (k === 'stack') continue\n out[k] = v\n }\n return out\n}\n\n/**\n * Coerce any unknown value into a TheoError envelope.\n *\n * - If value is already a TheoError, returns it unchanged (no double-wrap).\n * - If value is a plain Error, wraps it as `INTERNAL_SERVER_ERROR` with\n * `cause` chain preserving the original.\n * - Other values (string, undefined, object) are coerced safely with a\n * meaningful message.\n *\n * Use at framework boundaries (route handler catch-all, middleware catch,\n * SDK egress) to ensure every error crossing the wire carries an envelope.\n */\n/**\n * `NotFoundError` — typed 404. A `TheoError` with code `NOT_FOUND`, so it\n * carries a stable `code` (not a bare status literal) and maps to HTTP 404 via\n * `envelopeCodeToStatus`. Throw it from a `defineRoute` handler for ergonomic\n * 404s (M7-1).\n */\nexport class NotFoundError extends TheoError {\n constructor(message = 'Not Found', meta?: Record<string, unknown>) {\n super({ code: 'NOT_FOUND', message, ...(meta !== undefined ? { meta } : {}) })\n }\n}\n\nexport function fromUnknown(value: unknown): TheoError {\n if (value instanceof TheoError) return value\n if (value instanceof Error) {\n return new TheoError({\n code: 'INTERNAL_SERVER_ERROR',\n message: value.message,\n cause: value,\n })\n }\n if (typeof value === 'string') {\n return new TheoError({ code: 'INTERNAL_SERVER_ERROR', message: value })\n }\n if (value === undefined || value === null) {\n return new TheoError({ code: 'INTERNAL_SERVER_ERROR', message: 'Unknown error' })\n }\n // Object / number / boolean / etc. — best-effort stringify\n return new TheoError({\n code: 'INTERNAL_SERVER_ERROR',\n message: `Non-Error value thrown: ${safeStringify(value)}`,\n cause: value,\n })\n}\n\n/**\n * Best-effort stringify of an unknown value for error message context.\n * JSON.stringify first (preserves shape for diagnostics); on cycle/BigInt/symbol\n * failure, signal explicitly instead of falling back to `[object Object]`.\n */\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (typeof value === 'bigint') return `${value.toString()}n`\n if (typeof value === 'symbol') return value.toString()\n try {\n return JSON.stringify(value)\n } catch {\n return '[unstringifiable value]'\n }\n}\n","/**\n * G5 T2.5 — server-side boundary translation per blueprint ADR D3.\n *\n * Translates ad-hoc Error class instances (FileTooLargeError,\n * AuthRequiredError, RouterConventionError, etc.) into the canonical\n * `TheoErrorEnvelope` shape at the wire boundary. This is the SINGLE\n * boundary translation point — class identities stay in place inside the\n * codebase (no invasive call-site rewrites), but the envelope is what\n * crosses to clients.\n *\n * Pure; no I/O. Lives in `core/contracts/` per architecture.md v3\n * invariant #3 (canonical home for shared client↔server types). This file\n * is the EXCEPTION-of-an-exception: it intentionally inspects Error names\n * by string instead of `instanceof` to avoid `core/contracts/` taking a\n * dependency on `server/`. The mapping table is small and grep-discoverable.\n */\n\nimport type { TheoErrorCode, TheoErrorEnvelope } from './error-envelope.js'\nimport { TheoError } from './theo-error.js'\n\n/**\n * Mapping from ad-hoc Error class name to envelope code. Add entries here\n * when a new ad-hoc Error is introduced anywhere in `theokit/server/`.\n * Build-time errors (Duplicate*, ActionScanError, InvalidPluginShapeError)\n * are NOT in this table by design — they never cross the wire boundary,\n * so their canonical shape is `INTERNAL_SERVER_ERROR` via the default branch.\n */\nconst ERROR_NAME_TO_CODE: ReadonlyMap<string, TheoErrorCode> = new Map<string, TheoErrorCode>([\n ['AuthRequiredError', 'UNAUTHORIZED'],\n ['FileTooLargeError', 'PAYLOAD_TOO_LARGE'],\n ['RequestBodyTooLargeError', 'PAYLOAD_TOO_LARGE'],\n ['BodyTooLargeError', 'PAYLOAD_TOO_LARGE'],\n // RouterConventionError reaches the wire only during dev-error overlay;\n // INTERNAL_SERVER_ERROR is appropriate, but we add a HintExt-shaped ext\n // with the migration guidance for the devtools display.\n ['RouterConventionError', 'INTERNAL_SERVER_ERROR'],\n])\n\ntype MetaExtractor = (err: Error) => Record<string, unknown> | null\n\n/**\n * Class-specific metadata extractors. Each function receives the raw Error\n * and returns an object merged into envelope.meta (excluding name/message\n * which are reserved).\n */\nconst META_EXTRACTORS: ReadonlyMap<string, MetaExtractor> = new Map<string, MetaExtractor>([\n [\n 'FileTooLargeError',\n (err) => {\n const e = err as Error & { truncatedFilenames?: string[]; maxFileSize?: number }\n return {\n truncatedFilenames: e.truncatedFilenames ?? [],\n maxFileSize: e.maxFileSize,\n }\n },\n ],\n [\n 'RouterConventionError',\n (err) => {\n const e = err as Error & { file?: string; suggestion?: string; migrationUrl?: string }\n return {\n file: e.file,\n suggestion: e.suggestion,\n migrationUrl: e.migrationUrl,\n }\n },\n ],\n])\n\ntype ExtBuilder = (err: Error) => unknown\n\n/**\n * Class-specific ext (extension slot) builders. RouterConventionError ships a\n * HintExt-shaped `{ hint }` so devtools / dev overlay can render the actionable\n * migration tip directly.\n */\nconst EXT_BUILDERS: ReadonlyMap<string, ExtBuilder> = new Map<string, ExtBuilder>([\n [\n 'RouterConventionError',\n () => ({\n hint: 'Run `theokit migrate router` to convert dotted-basename routes to directory-nested form.',\n }),\n ],\n])\n\n/**\n * Translate any server-side Error instance into a canonical envelope.\n *\n * Pass-through behavior:\n * - `TheoError` instances return their `.envelope` unchanged.\n * - Plain `Error` with unknown name → `INTERNAL_SERVER_ERROR`.\n * - Non-Error values → wrapped via `TheoError.fromUnknown` semantics.\n *\n * Meta carries the source class `name` plus class-specific fields when an\n * extractor is registered. This keeps wire-side telemetry useful without\n * leaking implementation details.\n */\nexport function serverErrorToEnvelope(value: unknown): TheoErrorEnvelope {\n if (value instanceof TheoError) {\n return value.envelope\n }\n if (!(value instanceof Error)) {\n return {\n code: 'INTERNAL_SERVER_ERROR',\n message: typeof value === 'string' ? value : 'Unknown error',\n }\n }\n const name = value.name\n const code = ERROR_NAME_TO_CODE.get(name) ?? 'INTERNAL_SERVER_ERROR'\n const extractor = META_EXTRACTORS.get(name)\n const extra = extractor ? extractor(value) : null\n const meta: Record<string, unknown> = { name }\n if (extra) {\n for (const [k, v] of Object.entries(extra)) {\n if (v !== undefined) meta[k] = v\n }\n }\n const extBuilder = EXT_BUILDERS.get(name)\n const ext = extBuilder ? extBuilder(value) : undefined\n return {\n code,\n message: value.message,\n cause: (value as Error & { cause?: unknown }).cause,\n meta,\n ext,\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/define/health-route.ts"],"sourcesContent":["/**\n * M7-2 — health/ready reserved routes for the convention/filesystem-route\n * server. Liveness (`/__theo/health`, always 200) and readiness\n * (`/__theo/ready`, 200/503 from a probe) are registered on a reserved\n * namespace BEFORE the user-route catch-all + 404 branch — mirroring nitro's\n * `/_nitro/*` reserved-namespace pattern (knowledge-base/references/nitro/src/\n * runtime/internal/routes/dev-tasks.ts). Liveness and readiness are kept\n * separate by design: liveness says \"the process is up\", readiness says\n * \"dependencies are up\".\n *\n * @public\n */\n\n/** Reserved path for the liveness endpoint. */\nexport const HEALTH_PATH = '/__theo/health'\n/** Reserved path for the readiness endpoint. */\nexport const READY_PATH = '/__theo/ready'\n\n/** Config produced by {@link defineHealthRoute}. */\nexport interface HealthRouteConfig {\n readonly kind: 'health'\n /** Returns the liveness body (auto-serialized to JSON). Default: `{ status: 'ok' }`. */\n readonly handler: () => unknown\n}\n\n/** Config produced by {@link defineReadyRoute}. */\nexport interface ReadyRouteConfig {\n readonly kind: 'ready'\n /** Resolves `true` when dependencies are ready. A throw/reject is treated as not-ready (503). */\n readonly probe: () => boolean | Promise<boolean>\n}\n\n/** The reserved-route registry consulted by {@link serveReservedRoute}. */\nexport interface ReservedRoutes {\n readonly health?: HealthRouteConfig\n readonly ready?: ReadyRouteConfig\n}\n\n/** A reserved-route response: an HTTP status + a JSON-serializable body. */\nexport interface ReservedResponse {\n readonly status: number\n readonly body: unknown\n}\n\n/**\n * Define the liveness route. The default handler returns `{ status: 'ok' }`.\n * Liveness is always 200 — it asserts the process is up, not that dependencies are.\n */\nexport function defineHealthRoute(\n handler: () => unknown = () => ({ status: 'ok' }),\n): HealthRouteConfig {\n return { kind: 'health', handler }\n}\n\n/**\n * Define the readiness route. The `probe` decides 200 (ready) vs 503 (not-ready).\n * A probe that throws/rejects is treated as not-ready (503) — never a 500 crash.\n */\nexport function defineReadyRoute(probe: () => boolean | Promise<boolean>): ReadyRouteConfig {\n return { kind: 'ready', probe }\n}\n\n/**\n * Pure dispatcher: map a request pathname to a reserved-route response, or\n * `null` when the path is not reserved (so the user catch-all + 404 take over).\n * Liveness defaults to 200 `{status:'ok'}`; readiness without a probe is\n * trivially ready (200). Registered BEFORE the user catch-all by the caller.\n */\nexport async function serveReservedRoute(\n pathname: string,\n routes: ReservedRoutes = {},\n): Promise<ReservedResponse | null> {\n if (pathname === HEALTH_PATH) {\n const handler = routes.health?.handler ?? (() => ({ status: 'ok' }))\n return { status: 200, body: handler() }\n }\n if (pathname === READY_PATH) {\n const probe = routes.ready?.probe\n if (probe === undefined) {\n return { status: 200, body: { status: 'ready' } }\n }\n try {\n const ready = await probe()\n return ready\n ? { status: 200, body: { status: 'ready' } }\n : { status: 503, body: { status: 'not-ready' } }\n } catch (err) {\n // 503 (the dependency is not ready) but NEVER swallow the cause — a\n // readiness probe failing silently is invisible to ops (project rule:\n // \"NUNCA engula exceções\"). Log the cause, keep the 503 contract.\n console.error('[theo] readiness probe threw — treating as not-ready', err)\n return { status: 503, body: { status: 'not-ready' } }\n }\n }\n return null\n}\n"],"mappings":";AAcO,IAAM,cAAc;AAEpB,IAAM,aAAa;AAgCnB,SAAS,kBACd,UAAyB,OAAO,EAAE,QAAQ,KAAK,IAC5B;AACnB,SAAO,EAAE,MAAM,UAAU,QAAQ;AACnC;AAMO,SAAS,iBAAiB,OAA2D;AAC1F,SAAO,EAAE,MAAM,SAAS,MAAM;AAChC;AAQA,eAAsB,mBACpB,UACA,SAAyB,CAAC,GACQ;AAClC,MAAI,aAAa,aAAa;AAC5B,UAAM,UAAU,OAAO,QAAQ,YAAY,OAAO,EAAE,QAAQ,KAAK;AACjE,WAAO,EAAE,QAAQ,KAAK,MAAM,QAAQ,EAAE;AAAA,EACxC;AACA,MAAI,aAAa,YAAY;AAC3B,UAAM,QAAQ,OAAO,OAAO;AAC5B,QAAI,UAAU,QAAW;AACvB,aAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,QAAQ,EAAE;AAAA,IAClD;AACA,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM;AAC1B,aAAO,QACH,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,QAAQ,EAAE,IACzC,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,YAAY,EAAE;AAAA,IACnD,SAAS,KAAK;AAIZ,cAAQ,MAAM,6DAAwD,GAAG;AACzE,aAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,YAAY,EAAE;AAAA,IACtD;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/rate-limit/rate-limit-store.ts","../src/server/rate-limit/rate-limit.ts"],"sourcesContent":["/**\n * T2.1 — RateLimitStore interface.\n *\n * Pluggable backend for the rate limiter. The default `InMemoryStore`\n * preserves current single-instance behavior. Multi-instance deployments\n * (TheoCloud canary, K8s replicas) opt in to a distributed adapter\n * (Redis, Cloudflare KV) without bloating single-instance apps.\n *\n * Contract per ADR D1:\n * - `incr` is atomic — concurrent calls for the same key both observe\n * the same `resetAt` and increment count by 1.\n * - `get` returns `null` for expired entries (not just absent — checks\n * `now > resetAt`).\n * - `reset` removes the key entirely; next `incr` creates fresh.\n *\n * Async signature is honest about Redis adapters even though in-memory\n * implementation is synchronous. Callers MUST await.\n */\n\nexport interface RateLimitState {\n /** Number of requests counted in the current window. */\n count: number\n /** Absolute timestamp (ms since epoch) when this window expires. */\n resetAt: number\n}\n\nexport interface RateLimitStore {\n /**\n * Atomic increment-and-get. If the key is missing OR the previous\n * window expired (`now >= resetAt`), create with `count=1, resetAt=now+windowMs`.\n * Otherwise increment count by 1, preserving the original resetAt.\n */\n incr(key: string, windowMs: number): Promise<RateLimitState>\n\n /** Read current state. Returns `null` for missing or expired entries. */\n get(key: string): Promise<RateLimitState | null>\n\n /** Remove a key. Used by login throttling on success (T6.1). */\n reset(key: string): Promise<void>\n}\n\n/**\n * Default in-memory store. Backed by `Map<string, RateLimitState>`.\n *\n * GC: every 1000 `incr` calls, expired entries are removed. Pathological\n * key explosion is bounded by `MAX_ENTRIES` (LRU-evict oldest insertion).\n *\n * Single-thread by virtue of Node's event loop — `incr` is atomic at the\n * JavaScript level (no preemption between Map.get and Map.set).\n */\nexport class InMemoryStore implements RateLimitStore {\n private store = new Map<string, RateLimitState>()\n /** Bound the map to prevent unbounded growth from pathological inputs. */\n static readonly MAX_ENTRIES = 100_000\n /** GC sweep interval, milliseconds. */\n static readonly GC_INTERVAL_MS = 30_000\n\n private gcTimer: ReturnType<typeof setInterval> | null = null\n\n constructor() {\n // CR-007 fix: GC runs on a timer OUTSIDE the request hot path.\n // Pre-fix, every 1000th `incrSync` triggered a synchronous Map sweep\n // of up to 100K entries — an attacker with diverse client IPs could\n // keep the Map full and force a blocking sweep predictably.\n if (typeof setInterval !== 'undefined') {\n const timer = setInterval(() => {\n this.sweepExpired()\n }, InMemoryStore.GC_INTERVAL_MS)\n this.gcTimer = timer\n // Allow the process to exit even with the timer pending. `unref` is\n // Node-only; not present on browser/Cloudflare `setInterval`.\n const maybeUnref = (timer as { unref?: () => void }).unref\n if (typeof maybeUnref === 'function') maybeUnref.call(timer)\n }\n }\n\n /**\n * Synchronous fast-path used by the legacy sync `createRateLimiter`\n * surface (`api-middleware.ts` is sync). The async `incr` delegates to\n * this for in-memory; external adapters override `incr` directly.\n */\n incrSync(key: string, windowMs: number): RateLimitState {\n if (!Number.isFinite(windowMs) || windowMs <= 0) {\n throw new Error(\n `InMemoryStore.incr: windowMs must be a positive finite number (got ${windowMs})`,\n )\n }\n const now = Date.now()\n\n // Bounded LRU-ish: when over cap, drop oldest insertion.\n if (this.store.size >= InMemoryStore.MAX_ENTRIES) {\n const first = this.store.keys().next().value\n if (first !== undefined) this.store.delete(first)\n }\n\n const entry = this.store.get(key)\n if (!entry || now >= entry.resetAt) {\n const fresh = { count: 1, resetAt: now + windowMs }\n this.store.set(key, fresh)\n return { ...fresh }\n }\n entry.count++\n return { count: entry.count, resetAt: entry.resetAt }\n }\n\n /** Sweep expired entries. Called by the GC timer; safe to call manually. */\n sweepExpired(): void {\n const now = Date.now()\n for (const [k, v] of this.store) {\n if (v.resetAt <= now) this.store.delete(k)\n }\n }\n\n /**\n * Stop the GC timer. Call from tests or when discarding the store. After\n * `dispose`, the store still works but no longer auto-sweeps.\n */\n dispose(): void {\n if (this.gcTimer) {\n clearInterval(this.gcTimer)\n this.gcTimer = null\n }\n }\n\n // The async surface is required by the `RateLimitStore` interface\n // (Redis/etc. adapters are inherently async). For the in-memory case\n // we just adapt the sync implementations to Promises.\n // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return\n async incr(key: string, windowMs: number): Promise<RateLimitState> {\n return this.incrSync(key, windowMs)\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return\n async get(key: string): Promise<RateLimitState | null> {\n const now = Date.now()\n const entry = this.store.get(key)\n if (!entry) return null\n if (now >= entry.resetAt) return null\n return { count: entry.count, resetAt: entry.resetAt }\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return\n async reset(key: string): Promise<void> {\n this.store.delete(key)\n }\n}\n","import type { IncomingMessage } from 'node:http'\n\nimport { InMemoryStore, type RateLimitStore } from './rate-limit-store.js'\n\n/**\n * Rate limit configuration — basic single-bucket shape. Per ADR D2, the\n * per-route + per-user variant is layered on top via T2.2; this base\n * struct is the smallest unit.\n */\nexport interface RateLimitConfig {\n windowMs: number\n max: number\n}\n\nexport interface RateLimitResult {\n limited: boolean\n headers: Record<string, string>\n}\n\n/**\n * Create a rate limiter that consumes a pluggable `RateLimitStore`.\n *\n * Backwards-compatible signature: callers passing only `config` get the\n * default `InMemoryStore`. Distributed deployments pass a Redis adapter\n * (or any other `RateLimitStore` implementation) via `opts.store`.\n *\n * T2.1: synchronous return for back-compat with the current\n * `api-middleware.ts` integration point. The async store contract is\n * exercised lazily — we use a sync fast-path against the in-memory store\n * (single-thread JS makes this safe). External stores remain async and\n * would require a different async wrapper at the middleware layer.\n */\nexport function createRateLimiter(config: RateLimitConfig, opts: { store?: RateLimitStore } = {}) {\n const store = opts.store ?? new InMemoryStore()\n const isInMemory = store instanceof InMemoryStore\n\n return function checkRateLimit(req: IncomingMessage): RateLimitResult {\n // `req.socket` is typed as always-present in Node typings; defensive\n // for test doubles (object literals without `socket`).\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for test doubles\n const key = req.socket?.remoteAddress ?? 'unknown'\n\n if (isInMemory) {\n // Sync fast-path: the in-memory store exposes a sync incr.\n const state = store.incrSync(key, config.windowMs)\n return resultFromState(state, config)\n }\n\n // External stores are NOT supported via this synchronous façade in\n // 0.3.x. Users plugging Redis adapters today must wire it via a\n // dedicated async middleware path (out-of-scope for T2.1; tracked\n // for the follow-up `@theokit/rate-limit-redis` package).\n throw new Error(\n 'createRateLimiter: async RateLimitStore implementations are not supported by this sync façade. ' +\n 'Use the InMemoryStore default or build a custom middleware around the async store directly.',\n )\n }\n}\n\nfunction resultFromState(\n state: { count: number; resetAt: number },\n config: RateLimitConfig,\n): RateLimitResult {\n if (state.count > config.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': String(Math.max(0, config.max - state.count)),\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 2/3 — Web-Standards single-bucket rate limiter.\n *\n * Mirror of `createRateLimiter(config, opts)` returning a checker that\n * accepts `(clientIp: string)` instead of `(req: IncomingMessage)`. Web\n * `Request` has no `req.socket.remoteAddress` — the IP is resolved by\n * the caller per-runtime (Node adapter from socket; CF Workers from\n * `cf-connecting-ip`; etc., same convention as Phase D slice 1/3's\n * `DeriveKeyRequestContext.clientIp`).\n *\n * Same `RateLimitConfig`, same `InMemoryStore` default, same async\n * store rejection (use a dedicated async middleware for external stores).\n * Same `RateLimitResult` return shape.\n *\n * **Signature difference from `createRateLimiter`:** this Web factory's\n * checker takes a raw IP string instead of a Request object. The IP is\n * the ONLY input the bucket needs; passing a Request object would force\n * the caller to populate `request.headers.get('x-forwarded-for')` or\n * similar without giving us any safer extraction. KISS — accept the IP\n * directly, document the per-runtime resolution at the adapter boundary.\n */\nexport function createRateLimiterWeb(\n config: RateLimitConfig,\n opts: { store?: RateLimitStore } = {},\n) {\n const store = opts.store ?? new InMemoryStore()\n const isInMemory = store instanceof InMemoryStore\n\n return function checkRateLimitWeb(clientIp: string): RateLimitResult {\n const key = clientIp.length > 0 ? clientIp : 'unknown'\n if (isInMemory) {\n const state = store.incrSync(key, config.windowMs)\n return resultFromState(state, config)\n }\n throw new Error(\n 'createRateLimiterWeb: async RateLimitStore implementations are not supported by this sync façade. ' +\n 'Use the InMemoryStore default or build a custom middleware around the async store directly.',\n )\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/body-parser.ts","../src/server/observability/request-log.ts","../src/server/observability/logger.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { basename } from 'node:path'\n\nimport Busboy from 'busboy'\n\n// --- Types ---\n\nexport interface UploadedFile {\n fieldname: string\n filename: string\n encoding: string\n mimeType: string\n buffer: Buffer\n size: number\n}\n\nexport interface ParsedBody {\n fields: Record<string, string>\n files: UploadedFile[]\n json?: unknown\n}\n\nexport interface BodyParserOptions {\n maxFileSize?: number // bytes, default 10MB\n maxFiles?: number // default 10\n maxFieldSize?: number // bytes, default 1MB\n}\n\nconst METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH'])\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB\nconst DEFAULT_MAX_FILES = 10\nconst DEFAULT_MAX_FIELD_SIZE = 1 * 1024 * 1024 // 1MB\n\nexport class FileTooLargeError extends Error {\n readonly code = 'FILE_TOO_LARGE'\n readonly status = 413\n constructor(\n message: string,\n readonly truncatedFilenames: string[],\n readonly maxFileSize: number,\n ) {\n super(message)\n this.name = 'FileTooLargeError'\n }\n}\n\n// --- JSON parsing ---\n\n/**\n * Stash the parsed body on the IncomingMessage for the devtools forwarder\n * to read at log time. Symbol-keyed so it never leaks into user-visible\n * surfaces. Best-effort — failures (e.g. frozen req) are swallowed.\n */\nfunction stashBodyPreview(req: IncomingMessage, body: unknown): void {\n try {\n ;(req as unknown as Record<symbol, unknown>)[DEVTOOLS_BODY_PREVIEW] = body\n } catch {\n // best-effort; never break the request\n }\n}\n\nfunction parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString()\n if (!raw) {\n resolve(undefined)\n return\n }\n try {\n resolve(JSON.parse(raw))\n } catch {\n reject(new Error('Invalid JSON body'))\n }\n })\n req.on('error', reject)\n })\n}\n\n// --- Multipart parsing ---\n\nfunction parseMultipartBody(\n req: IncomingMessage,\n contentType: string,\n options: Required<Pick<BodyParserOptions, 'maxFileSize' | 'maxFiles' | 'maxFieldSize'>>,\n): Promise<{ fields: Record<string, string>; files: UploadedFile[] }> {\n // EC-3: Validate boundary exists.\n if (!contentType.includes('boundary=')) {\n return Promise.reject(new Error('Missing multipart boundary'))\n }\n\n return new Promise((resolve, reject) => {\n const fields: Record<string, string> = {}\n const files: UploadedFile[] = []\n // CR-010: track filenames that were truncated mid-stream. The previous\n // implementation skipped truncated files from `files` and relied on\n // `files.some(f => f.size > maxFileSize)` to detect — but since the\n // file was never added, the guard never fired. Silent data loss.\n const truncatedFilenames: string[] = []\n let fileCount = 0\n\n const bb = Busboy({\n headers: req.headers,\n limits: {\n fileSize: options.maxFileSize,\n files: options.maxFiles,\n fieldSize: options.maxFieldSize,\n },\n })\n\n bb.on('field', (name: string, value: string) => {\n fields[name] = value\n })\n\n bb.on(\n 'file',\n (\n fieldname: string,\n stream: NodeJS.ReadableStream,\n info: { filename: string; encoding: string; mimeType: string },\n ) => {\n fileCount++\n if (fileCount > options.maxFiles) {\n stream.resume() // drain\n return\n }\n\n // EC-6: Sanitize filename — basename only, no path traversal.\n const safeName = basename(info.filename || 'unnamed')\n const chunks: Buffer[] = []\n let size = 0\n let truncated = false\n\n stream.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > options.maxFileSize) {\n truncated = true\n stream.resume() // drain the rest\n return\n }\n chunks.push(chunk)\n })\n\n stream.on('end', () => {\n if (truncated) {\n truncatedFilenames.push(safeName)\n return\n }\n\n const buffer = Buffer.concat(chunks)\n files.push({\n fieldname,\n filename: safeName,\n encoding: info.encoding,\n mimeType: info.mimeType,\n buffer,\n size: buffer.length,\n })\n })\n },\n )\n\n bb.on('filesLimit', () => {\n reject(new Error(`Too many files. Maximum: ${options.maxFiles}`))\n })\n\n bb.on('error', (err: Error) => {\n reject(err)\n })\n\n bb.on('close', () => {\n if (truncatedFilenames.length > 0) {\n reject(\n new FileTooLargeError(\n `File too large (max ${options.maxFileSize} bytes): ${truncatedFilenames.join(', ')}`,\n truncatedFilenames,\n options.maxFileSize,\n ),\n )\n return\n }\n resolve({ fields, files })\n })\n\n req.pipe(bb)\n })\n}\n\n// --- Main parser ---\n\n/**\n * Symbol-keyed slot on IncomingMessage that the devtools request log\n * forwarder reads to enrich the broadcast payload with a body preview.\n * Symbol-keyed so it can't collide with user-land props and is invisible\n * to JSON.stringify / iteration. Always optional — readers must guard.\n */\nexport const DEVTOOLS_BODY_PREVIEW = Symbol('theo:devtools:bodyPreview')\n\nexport async function parseRequestBody(\n req: IncomingMessage,\n options?: BodyParserOptions,\n): Promise<ParsedBody> {\n const method = req.method?.toUpperCase() ?? 'GET'\n if (!METHODS_WITH_BODY.has(method)) {\n return { fields: {}, files: [], json: undefined }\n }\n\n const contentType = req.headers['content-type'] ?? ''\n\n // JSON\n if (contentType.includes('application/json')) {\n const json = await parseJsonBody(req)\n stashBodyPreview(req, json)\n return { fields: {}, files: [], json }\n }\n\n // Multipart\n if (contentType.includes('multipart/form-data')) {\n const limits = {\n maxFileSize: options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,\n maxFiles: options?.maxFiles ?? DEFAULT_MAX_FILES,\n maxFieldSize: options?.maxFieldSize ?? DEFAULT_MAX_FIELD_SIZE,\n }\n const { fields, files } = await parseMultipartBody(req, contentType, limits)\n stashBodyPreview(req, {\n fields,\n files: files.map((f) => ({\n fieldname: f.fieldname,\n filename: f.filename,\n mimeType: f.mimeType,\n size: f.size,\n })),\n })\n return { fields, files }\n }\n\n // No body or empty\n if (!contentType) {\n return { fields: {}, files: [] }\n }\n\n // Unsupported content type\n throw new Error(`Unsupported Content-Type: ${contentType}`)\n}\n","/**\n * Request log primitive (T6.2 of architecture-review-remediation-plan).\n *\n * Split out from `logger.ts` per PV-12 (SRP) — the file previously mixed\n * 3 concerns (factory, warnOnce, request log). This module owns ONLY the\n * `logRequest` flow + the devtools broadcast best-effort forwarder.\n *\n * Public API surface is unchanged — `logger.ts` re-exports `logRequest`\n * + `RequestLog` + `LoggerFn` for backward compat.\n */\nimport type { IncomingMessage } from 'node:http'\n\nimport { DEVTOOLS_BODY_PREVIEW } from '../body-parser.js'\n\nexport interface RequestLog {\n level: string\n method: string\n url: string\n status: number\n duration: number\n requestId: string\n timestamp: string\n}\n\nexport type LoggerFn = (log: RequestLog) => void\n\nconst defaultLoggerFn: LoggerFn = (log) => {\n // eslint-disable-next-line no-console -- request-log default output IS the contract\n console.log(JSON.stringify(log))\n}\n\nexport function logRequest(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n customLogger?: LoggerFn,\n req?: IncomingMessage,\n): void {\n const log: RequestLog = {\n level: 'info',\n ...info,\n timestamp: new Date().toISOString(),\n }\n const logger = customLogger ?? defaultLoggerFn\n logger(log)\n // T2.1 — also broadcast to devtools (no-op in prod / when no dev server).\n // Pass `req` so the devtools forwarder can extract headers + body preview\n // stashed by body-parser (Symbol-keyed). Without `req`, only metadata\n // (method/path/status/duration) reaches the devtools UI — the expanded\n // row view then shows nothing useful.\n broadcastRequestToDevtools(info, req)\n}\n\n/**\n * T2.1 — Forward a request record to the devtools UI. Lazy import keeps\n * this module dep-free for prod; broadcast.ts internally guards on\n * `globalThis.__theoViteHotServer`.\n */\nfunction randomRequestId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id for devtools UI\n return `req-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * T5a.2 Phase C slice 2/2 — pure devtools broadcast that takes a\n * pre-extracted headers object + optional stashed body, so both\n * IncomingMessage and Web Request paths share the same dispatch logic.\n *\n * Errors here must not propagate — devtools forwarding is best-effort\n * and silenced.\n */\nfunction broadcastToDevtoolsCore(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n headers: Record<string, string> | undefined,\n stashedBody: unknown,\n): void {\n void Promise.all([\n import('../../devtools/server-side/broadcast.js'),\n import('../../devtools/server-side/build-request-body-preview.js'),\n ])\n .then(([{ broadcastRequest }, { buildRequestBodyPreview }]) => {\n const bodyInfo = buildRequestBodyPreview(stashedBody)\n broadcastRequest({\n id: randomRequestId(),\n traceId: info.requestId,\n method: info.method,\n path: info.url,\n status: info.status,\n durationMs: info.duration,\n startedAt: Date.now() - info.duration,\n ...(headers ? { headers } : {}),\n ...(bodyInfo\n ? {\n bodyPreview: bodyInfo.preview,\n bodyLength: bodyInfo.length,\n bodyTruncated: bodyInfo.truncated,\n }\n : {}),\n })\n })\n .catch(() => {\n // No-op: devtools forwarding is best-effort.\n })\n}\n\nfunction broadcastRequestToDevtools(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n req?: IncomingMessage,\n): void {\n // T2.1 forwarder. Errors here must not propagate.\n const headers = req?.headers\n ? Object.fromEntries(\n Object.entries(req.headers).map(([k, v]) => [\n k,\n Array.isArray(v) ? v.join(', ') : (v ?? ''),\n ]),\n )\n : undefined\n const stashedBody = req\n ? (req as unknown as Record<symbol, unknown>)[DEVTOOLS_BODY_PREVIEW]\n : undefined\n broadcastToDevtoolsCore(info, headers, stashedBody)\n}\n\n/**\n * T5a.2 Phase C slice 2/2 — Web-Standards request log + devtools\n * forward. Mirror of `logRequest(info, logger?, req?: IncomingMessage)`\n * accepting `request?: Request`.\n *\n * Extracts headers via `request.headers.entries()` (Web `Headers`\n * iterator). Body preview stash is NOT yet supported on the Web path —\n * `body-parser-web.ts` doesn't yet stash like `body-parser.ts` does for\n * the IncomingMessage path. That hookup is Phase E scope (body parsing\n * migration). Until then, the devtools UI shows headers but no body\n * preview for Web-handled requests — same surface as the IncomingMessage\n * path before the stash convention shipped.\n */\nexport function logRequestFromRequest(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n customLogger?: LoggerFn,\n request?: Request,\n): void {\n const log: RequestLog = {\n level: 'info',\n ...info,\n timestamp: new Date().toISOString(),\n }\n const logger = customLogger ?? defaultLoggerFn\n logger(log)\n\n let headers: Record<string, string> | undefined\n if (request) {\n headers = {}\n for (const [k, v] of request.headers.entries()) {\n headers[k] = v\n }\n }\n // Phase E will add body-preview stash on Web Request; for now no stash.\n broadcastToDevtoolsCore(info, headers, undefined)\n}\n","// --- Types ---\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'\n\nexport interface StructuredLog {\n level: string\n msg: string\n timestamp: string\n [key: string]: unknown\n}\n\nexport interface TheoLogger {\n debug(msg: string, context?: Record<string, unknown>): void\n info(msg: string, context?: Record<string, unknown>): void\n warn(msg: string, context?: Record<string, unknown>): void\n error(msg: string, context?: Record<string, unknown>): void\n child(context: Record<string, unknown>): TheoLogger\n}\n\n// --- Level filtering ---\n\nconst LEVEL_ORDER: Record<string, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4,\n}\n\nfunction shouldLog(msgLevel: string, configLevel: string): boolean {\n return LEVEL_ORDER[msgLevel] >= LEVEL_ORDER[configLevel]\n}\n\n// --- Default output ---\n\nfunction defaultOutput(log: StructuredLog): void {\n // eslint-disable-next-line no-console -- structured logger output IS the contract here\n console.log(JSON.stringify(log))\n}\n\n// --- Factory ---\n\nexport function createLogger(options?: {\n level?: LogLevel\n output?: (log: StructuredLog) => void\n context?: Record<string, unknown>\n}): TheoLogger {\n const level = options?.level ?? 'info'\n const output = options?.output ?? defaultOutput\n const baseContext = options?.context ?? {}\n\n function log(msgLevel: string, msg: string, context?: Record<string, unknown>): void {\n if (!shouldLog(msgLevel, level)) return\n\n const entry: StructuredLog = {\n level: msgLevel,\n msg,\n timestamp: new Date().toISOString(),\n ...baseContext,\n ...context,\n }\n\n // Serialize Error objects to stack string\n if (entry.error instanceof Error) {\n entry.error = entry.error.stack ?? entry.error.message\n }\n\n output(entry)\n }\n\n return {\n debug: (msg, ctx) => {\n log('debug', msg, ctx)\n },\n info: (msg, ctx) => {\n log('info', msg, ctx)\n },\n warn: (msg, ctx) => {\n log('warn', msg, ctx)\n },\n error: (msg, ctx) => {\n log('error', msg, ctx)\n },\n child(ctx) {\n return createLogger({\n level,\n output,\n context: { ...baseContext, ...ctx },\n })\n },\n }\n}\n\n// --- warnOnce (T2.1) ---\n\n/**\n * Per-key dedup state. Module-scoped — reset per-process at boot.\n *\n * CR-011 fix: previous implementation used an unbounded `Set` which grew\n * forever in long-running prod processes with high-cardinality keys\n * (e.g. dynamic path segments). The bound below uses LRU-by-insertion:\n * when the Set hits `MAX_SEEN`, the OLDEST key is evicted. Subsequent\n * occurrences of that key will warn ONCE more — acceptable in exchange\n * for bounded memory.\n */\nconst _warnOnceSeen = new Set<string>()\nconst MAX_SEEN = 1024\n\n/**\n * Reset internal state. Test-only export — do not call from production.\n */\nexport function _resetWarnOnceForTests(): void {\n _warnOnceSeen.clear()\n}\n\n/**\n * Emit a structured warning ONCE per key. Subsequent calls with the same\n * key are suppressed. Used for cutover-style warnings (e.g. CSRF warn)\n * that would otherwise flood logs under load.\n *\n * Convention: key is `<event>:<method>:<path>` — see callers in csrf.ts.\n *\n * EC-2: payload may contain circular references. Wrap `JSON.stringify`\n * in try/catch so a malformed payload doesn't crash the request handler.\n */\nexport function warnOnce(key: string, payload: Record<string, unknown>): void {\n if (_warnOnceSeen.has(key)) return\n // CR-011: bounded LRU. Evict oldest insertion when at capacity.\n if (_warnOnceSeen.size >= MAX_SEEN) {\n const oldest = _warnOnceSeen.values().next().value\n if (oldest !== undefined) _warnOnceSeen.delete(oldest)\n }\n _warnOnceSeen.add(key)\n\n let line: string\n try {\n line = JSON.stringify({ ...payload, warnOnce: true })\n } catch {\n // EC-2 fallback — preserve the key for grep-ability even when payload\n // can't serialize (e.g., circular references, BigInt, etc.).\n line = `[warnOnce] ${key} — (payload could not be serialized)`\n }\n console.warn(line)\n\n // T2.1 — also broadcast to devtools (no-op in prod / when no dev server).\n // Imported lazily to keep logger.ts dependency-free for non-dev contexts;\n // broadcastToDevtools internally guards on globalThis.__theoViteHotServer.\n broadcastWarnOnceToDevtools(key, payload)\n}\n\n/**\n * T2.1 — Forward a warnOnce payload to the devtools UI. Recognizes the\n * canonical CSRF warn shape and dispatches as 'csrf.warn'; otherwise\n * falls back to a generic 'error' broadcast.\n *\n * No-op when:\n * - globalThis.__theoViteHotServer is undefined (prod / no dev server)\n * - any error occurs (default-deny via broadcastToDevtools' try/catch)\n */\nfunction payloadField(payload: Record<string, unknown>, key: string, fallback = ''): string {\n const value = payload[key]\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return fallback\n}\n\nfunction randomDevtoolsId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id for devtools UI\n return `warn-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction broadcastWarnOnceToDevtools(key: string, payload: Record<string, unknown>): void {\n // CR-021: removed dead `void key` statement.\n // T2.1 forwarder. Errors here must not propagate (default-deny).\n void import('../../devtools/server-side/broadcast.js')\n .then(({ broadcastCsrfWarn, broadcastError }) => {\n if (\n payload.event === 'csrf.warn' &&\n typeof payload.code === 'string' &&\n typeof payload.docsUrl === 'string'\n ) {\n broadcastCsrfWarn({\n event: 'csrf.warn',\n code: payload.code,\n docsUrl: payload.docsUrl,\n method: payloadField(payload, 'method'),\n path: payloadField(payload, 'path'),\n reason: payloadField(payload, 'reason'),\n })\n } else {\n broadcastError({\n id: randomDevtoolsId(),\n type: 'console',\n message: payloadField(payload, 'event', key),\n timestamp: Date.now(),\n })\n }\n })\n .catch(() => {\n // No-op: devtools forwarding is best-effort.\n })\n}\n\n// --- Backward compat re-exports ---\n//\n// T6.2 (PV-12 SRP): `logRequest` + `RequestLog` + `LoggerFn` moved to a\n// dedicated `request-log.ts`. Re-exported here so consumers importing\n// from `theokit/server` see no change.\n\nexport { logRequest } from './request-log.js'\nexport type { RequestLog, LoggerFn } from './request-log.js'\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/contracts/http-methods.ts"],"sourcesContent":["/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n"],"mappings":";AAUO,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,wBAAwB,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/contracts/envelope-code-to-status.ts"],"sourcesContent":["/**\n * Maps a TheoErrorEnvelope `code` to its canonical HTTP status.\n *\n * Single source of truth — previously duplicated in `web-handler.ts` and\n * `handle-request-error.ts` (architecture-remediation plan T1.2, 2026-06-12).\n *\n * Every TheoErrorCode value MUST have an explicit entry here. SDK-domain codes\n * (AGENT_RUN_ERROR, PROVIDER_KEY_MISSING, BUDGET_EXCEEDED, CREDENTIAL_POOL_EXHAUSTED)\n * intentionally map to 500 — they represent internal failures, not client errors.\n */\n\nconst CODE_TO_STATUS: Record<string, number> = {\n // 4xx — client errors\n BAD_REQUEST: 400,\n UNAUTHORIZED: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n METHOD_NOT_ALLOWED: 405,\n CONFLICT: 409,\n PRECONDITION_FAILED: 412,\n PAYLOAD_TOO_LARGE: 413,\n UNSUPPORTED_MEDIA_TYPE: 415,\n UNPROCESSABLE_ENTITY: 422,\n TOO_MANY_REQUESTS: 429,\n RATE_LIMITED: 429,\n\n // 5xx — server errors\n INTERNAL_SERVER_ERROR: 500,\n NOT_IMPLEMENTED: 501,\n BAD_GATEWAY: 502,\n SERVICE_UNAVAILABLE: 503,\n GATEWAY_TIMEOUT: 504,\n\n // SDK-domain codes — intentionally 500\n AGENT_RUN_ERROR: 500,\n PROVIDER_KEY_MISSING: 500,\n BUDGET_EXCEEDED: 500,\n CREDENTIAL_POOL_EXHAUSTED: 500,\n}\n\nexport function envelopeCodeToStatus(code: string): number {\n return CODE_TO_STATUS[code] ?? 500\n}\n"],"mappings":";AAWA,IAAM,iBAAyC;AAAA;AAAA,EAE7C,aAAa;AAAA,EACb,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,wBAAwB;AAAA,EACxB,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,cAAc;AAAA;AAAA,EAGd,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,qBAAqB;AAAA,EACrB,iBAAiB;AAAA;AAAA,EAGjB,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,2BAA2B;AAC7B;AAEO,SAAS,qBAAqB,MAAsB;AACzD,SAAO,eAAe,IAAI,KAAK;AACjC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/devtools/shared.ts","../src/devtools/bridge/dispatcher.ts"],"sourcesContent":["/**\n * Devtools — shared types.\n *\n * NEVER use dangerouslySetInnerHTML in any devtools component — see plan EC-20.\n */\n\nexport type DevtoolsPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\nexport type DevtoolsTab =\n | 'requests'\n | 'routes'\n | 'agents'\n | 'actions'\n | 'errors'\n | 'csrf-readiness'\n | 'settings'\nexport type DevtoolsTheme = 'light' | 'dark' | 'system'\n\nexport interface RequestRecord {\n id: string\n traceId: string\n method: string\n path: string\n status: number\n durationMs: number\n startedAt: number\n csrfWarn?: { code: string; docsUrl: string }\n headers?: Record<string, string>\n bodyPreview?: string\n bodyLength?: number\n bodyTruncated?: boolean\n}\n\nexport interface ErrorRecord {\n id: string\n type: 'console' | 'unhandled' | 'csrf.warn'\n message: string\n stack?: string\n code?: string\n docsUrl?: string\n timestamp: number\n}\n\nexport interface CsrfWarnPayload {\n event: 'csrf.warn'\n code: string\n docsUrl: string\n method: string\n path: string\n reason: string\n}\n\nexport interface RouteInfo {\n path: string\n absoluteFilePath: string\n layoutChain: string[]\n hasLoading: boolean\n hasError: boolean\n hasNotFound: boolean\n}\n\nexport interface RouteManifest {\n routes: RouteInfo[]\n}\n\n/**\n * Per-agent-run telemetry surfaced to the Agents devtools tab.\n *\n * Emitted by `trackAgentRun` (server-side) via the dispatcher in dev mode;\n * prod tree-shakes the entire wire (v1.1 EC-4 `__IS_DEV` IIFE guard).\n * Type lives here (devtools/shared) since both producer (server/cost) and\n * consumer (AgentsTab) need the shape; following architecture v3 ADR-0001\n * rule \"shared types in core/contracts\" is the canonical home — for this\n * one we keep it in devtools/shared to minimize churn in core/contracts/.\n */\nexport interface AgentRunRecord {\n id: string\n timestamp: number\n userId: string\n model: string\n tokensInput: number\n tokensOutput: number\n costUsd: number\n status: 'finished' | 'error' | 'aborted'\n}\n\n/**\n * T5.1 — Per-action-call telemetry surfaced to the Actions devtools tab.\n *\n * Emitted by `action-execute.ts` (server-side) via the dispatcher in dev\n * mode; prod tree-shakes the wire (mirrors AgentRunRecord pattern).\n * Field-level error pins consume `error.fields` directly when status='error'\n * (per plan ADR D6).\n */\nexport interface ActionCallRecord {\n id: string\n timestamp: number\n name: string\n input: unknown\n output?: unknown\n error?: {\n code: string\n message: string\n fields?: Record<string, string[]>\n issues?: unknown[]\n }\n durationMs: number\n status: 'success' | 'error'\n}\n\nexport interface DevtoolsState {\n open: boolean\n visible: boolean\n position: DevtoolsPosition\n theme: DevtoolsTheme\n activeTab: DevtoolsTab\n requests: RequestRecord[]\n errors: ErrorRecord[]\n agentRuns: AgentRunRecord[]\n actionCalls: ActionCallRecord[]\n routeManifest: RouteManifest | null\n activeRoutePath: string | null\n activeChain: string[]\n agentStreamEvents: AgentStreamRecord[]\n}\n\nexport type DevtoolsAction =\n | { type: 'TOGGLE_PANEL' }\n | { type: 'TOGGLE_VISIBLE' }\n | { type: 'SET_TAB'; tab: DevtoolsTab }\n | { type: 'SET_POSITION'; position: DevtoolsPosition }\n | { type: 'SET_THEME'; theme: DevtoolsTheme }\n | { type: 'REQUEST_ADD'; request: RequestRecord }\n | { type: 'ERROR_ADD'; error: ErrorRecord }\n | { type: 'CSRF_WARN'; payload: CsrfWarnPayload }\n | { type: 'MANIFEST_UPDATED'; manifest: RouteManifest }\n | { type: 'ROUTE_MATCHED'; path: string; chain: string[] }\n | { type: 'AGENT_RUN_ADD'; run: AgentRunRecord }\n | { type: 'ACTION_CALL_ADD'; record: ActionCallRecord }\n | { type: 'RESET_REQUESTS' }\n | { type: 'RESET_ERRORS' }\n | { type: 'RESET_AGENT_RUNS' }\n | { type: 'RESET_ACTION_CALLS' }\n | { type: 'AGENT_STREAM_EVENT'; event: AgentStreamRecord }\n | { type: 'RESET_AGENT_STREAM' }\n\nexport const RING_BUFFER_CAP = 50\nexport const MAX_QUEUE_SIZE = 100\nexport const STORAGE_VERSION = 2\n\nexport const initialState: DevtoolsState = {\n open: false,\n visible: true,\n position: 'bottom-right',\n theme: 'dark',\n activeTab: 'requests',\n requests: [],\n errors: [],\n agentRuns: [],\n actionCalls: [],\n routeManifest: null,\n activeRoutePath: null,\n activeChain: [],\n agentStreamEvents: [],\n}\n\nexport const CHANNEL_REQUEST = 'theo:devtools:request' as const\nexport const CHANNEL_ERROR = 'theo:devtools:error' as const\nexport const CHANNEL_CSRF_WARN = 'theo:devtools:csrf.warn' as const\nexport const CHANNEL_MANIFEST = 'theo:devtools:manifest' as const\nexport const CHANNEL_ROUTE_MATCHED = 'theo:devtools:route-matched' as const\nexport const CHANNEL_AGENT_STREAM = 'theo:devtools:agent.stream' as const\n\nexport interface AgentStreamRecord {\n runId: string\n agentName: string\n type: string\n content?: string\n toolName?: string\n timestamp: number\n}\n","/**\n * T2.1 — Devtools dispatcher with pre-mount event queue.\n *\n * Pattern: events emitted from anywhere (server WS, browser console.error,\n * dispatcher.onCsrfWarn) can fire BEFORE the React tree mounts. Each call\n * either dispatches immediately (when `setDispatch` registered a dispatch\n * fn) or queues until React is ready, then flushes on first mount.\n *\n * EC-23: queue capped at MAX_QUEUE_SIZE (FIFO eviction).\n * EC-24: setDispatch is idempotent — flush only on NULL → non-null\n * transition (StrictMode re-mount safe).\n *\n * NEVER use dangerouslySetInnerHTML in any devtools component — see plan EC-20.\n */\nimport {\n type ActionCallRecord,\n type AgentRunRecord,\n type AgentStreamRecord,\n type CsrfWarnPayload,\n type DevtoolsAction,\n type ErrorRecord,\n MAX_QUEUE_SIZE,\n type RequestRecord,\n type RouteManifest,\n} from '../shared.js'\n\ntype Dispatch = (action: DevtoolsAction) => void\ntype QueuedItem = (d: Dispatch) => void\n\nlet _dispatch: Dispatch | null = null\nconst _queue: QueuedItem[] = []\n\nfunction queuable<Args extends unknown[]>(\n fn: (d: Dispatch, ...args: Args) => void,\n): (...args: Args) => void {\n return (...args: Args) => {\n if (_dispatch) {\n try {\n fn(_dispatch, ...args)\n } catch (err) {\n // EC-25: an error inside reducer-bound dispatch path MUST NOT bubble\n // to the original event source (e.g. logger / HMR callback / global\n // error handler). Log and continue.\n\n console.error('[theo devtools] dispatch failed', err)\n }\n } else {\n // EC-23: cap queue, FIFO-evict if full\n if (_queue.length >= MAX_QUEUE_SIZE) _queue.shift()\n _queue.push((d) => {\n fn(d, ...args)\n })\n }\n }\n}\n\nfunction flushQueue(): void {\n while (_queue.length) {\n const fn = _queue.shift()\n if (!fn || !_dispatch) break\n try {\n fn(_dispatch)\n } catch (err) {\n console.error('[theo devtools] queued event failed', err)\n }\n }\n}\n\nexport const dispatcher = {\n onRequest: queuable((d: Dispatch, req: RequestRecord) => {\n d({ type: 'REQUEST_ADD', request: req })\n }),\n onError: queuable((d: Dispatch, err: ErrorRecord) => {\n d({ type: 'ERROR_ADD', error: err })\n }),\n onCsrfWarn: queuable((d: Dispatch, payload: CsrfWarnPayload) => {\n d({ type: 'CSRF_WARN', payload })\n }),\n onManifestUpdated: queuable((d: Dispatch, manifest: RouteManifest) => {\n d({ type: 'MANIFEST_UPDATED', manifest })\n }),\n onRouteMatched: queuable((d: Dispatch, path: string, chain: string[]) => {\n d({ type: 'ROUTE_MATCHED', path, chain })\n }),\n onAgentRun: queuable((d: Dispatch, run: AgentRunRecord) => {\n d({ type: 'AGENT_RUN_ADD', run })\n }),\n onActionCall: queuable((d: Dispatch, record: ActionCallRecord) => {\n d({ type: 'ACTION_CALL_ADD', record })\n }),\n onAgentStreamEvent: queuable((d: Dispatch, event: AgentStreamRecord) => {\n d({ type: 'AGENT_STREAM_EVENT', event })\n }),\n\n /**\n * Wire React's dispatch function in. Idempotent (EC-24): only the\n * NULL → non-null transition flushes the queue; subsequent non-null\n * sets just replace the reference (used for StrictMode re-mount).\n */\n setDispatch(d: Dispatch | null): void {\n const prev = _dispatch\n _dispatch = d\n if (d && !prev) flushQueue()\n },\n\n /** Testing helper — clear queue + dispatch reference between tests. */\n _reset(): void {\n _dispatch = null\n _queue.length = 0\n },\n\n /** Testing helper — observe queue length. */\n _queueLength(): number {\n return _queue.length\n },\n}\n\nexport type Dispatcher = typeof dispatcher\n"],"mappings":";AAiJO,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAExB,IAAM,eAA8B;AAAA,EACzC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU,CAAC;AAAA,EACX,QAAQ,CAAC;AAAA,EACT,WAAW,CAAC;AAAA,EACZ,aAAa,CAAC;AAAA,EACd,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,aAAa,CAAC;AAAA,EACd,mBAAmB,CAAC;AACtB;AAEO,IAAM,kBAAkB;AACxB,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAC9B,IAAM,uBAAuB;;;AC7IpC,IAAI,YAA6B;AACjC,IAAM,SAAuB,CAAC;AAE9B,SAAS,SACP,IACyB;AACzB,SAAO,IAAI,SAAe;AACxB,QAAI,WAAW;AACb,UAAI;AACF,WAAG,WAAW,GAAG,IAAI;AAAA,MACvB,SAAS,KAAK;AAKZ,gBAAQ,MAAM,mCAAmC,GAAG;AAAA,MACtD;AAAA,IACF,OAAO;AAEL,UAAI,OAAO,UAAU,eAAgB,QAAO,MAAM;AAClD,aAAO,KAAK,CAAC,MAAM;AACjB,WAAG,GAAG,GAAG,IAAI;AAAA,MACf,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,aAAmB;AAC1B,SAAO,OAAO,QAAQ;AACpB,UAAM,KAAK,OAAO,MAAM;AACxB,QAAI,CAAC,MAAM,CAAC,UAAW;AACvB,QAAI;AACF,SAAG,SAAS;AAAA,IACd,SAAS,KAAK;AACZ,cAAQ,MAAM,uCAAuC,GAAG;AAAA,IAC1D;AAAA,EACF;AACF;AAEO,IAAM,aAAa;AAAA,EACxB,WAAW,SAAS,CAAC,GAAa,QAAuB;AACvD,MAAE,EAAE,MAAM,eAAe,SAAS,IAAI,CAAC;AAAA,EACzC,CAAC;AAAA,EACD,SAAS,SAAS,CAAC,GAAa,QAAqB;AACnD,MAAE,EAAE,MAAM,aAAa,OAAO,IAAI,CAAC;AAAA,EACrC,CAAC;AAAA,EACD,YAAY,SAAS,CAAC,GAAa,YAA6B;AAC9D,MAAE,EAAE,MAAM,aAAa,QAAQ,CAAC;AAAA,EAClC,CAAC;AAAA,EACD,mBAAmB,SAAS,CAAC,GAAa,aAA4B;AACpE,MAAE,EAAE,MAAM,oBAAoB,SAAS,CAAC;AAAA,EAC1C,CAAC;AAAA,EACD,gBAAgB,SAAS,CAAC,GAAa,MAAc,UAAoB;AACvE,MAAE,EAAE,MAAM,iBAAiB,MAAM,MAAM,CAAC;AAAA,EAC1C,CAAC;AAAA,EACD,YAAY,SAAS,CAAC,GAAa,QAAwB;AACzD,MAAE,EAAE,MAAM,iBAAiB,IAAI,CAAC;AAAA,EAClC,CAAC;AAAA,EACD,cAAc,SAAS,CAAC,GAAa,WAA6B;AAChE,MAAE,EAAE,MAAM,mBAAmB,OAAO,CAAC;AAAA,EACvC,CAAC;AAAA,EACD,oBAAoB,SAAS,CAAC,GAAa,UAA6B;AACtE,MAAE,EAAE,MAAM,sBAAsB,MAAM,CAAC;AAAA,EACzC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOD,YAAY,GAA0B;AACpC,UAAM,OAAO;AACb,gBAAY;AACZ,QAAI,KAAK,CAAC,KAAM,YAAW;AAAA,EAC7B;AAAA;AAAA,EAGA,SAAe;AACb,gBAAY;AACZ,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA,EAGA,eAAuB;AACrB,WAAO,OAAO;AAAA,EAChB;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/_internal/atomic-write.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time atomic write: caller-controlled paths only. No HTTP input\n * reaches these fs calls.\n */\n// T5a.1b — Web Crypto migration. Build-time only; node:fs + node:path stay\n// because this is a manifest-write leaf (e.g. .theokit/jobs.json) and per\n// ADR-0028 the runtime-portable boundary is the request handler, not the\n// scanner. Only node:crypto is swapped out — Web Crypto's\n// getRandomValues works in every supported runtime.\nimport { mkdirSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\n/**\n * Write `content` to `path` atomically via the tmp + rename pattern.\n *\n * Two concurrent calls to `writeAtomic(path, ...)` are guaranteed to\n * leave `path` containing valid content from ONE of the calls (never\n * truncated, never interleaved). POSIX rename is atomic on the same\n * filesystem.\n *\n * EC-106 (jobs-crons-webhooks-cost-tracking-plan) — shared helper for\n * `.theokit/crons.json` and `.theokit/jobs.json` manifest writes so a\n * concurrent dev-server scan + build manifest emit never produces\n * partial JSON.\n *\n * @param path destination path\n * @param content bytes (UTF-8 string) to write\n */\nexport function writeAtomic(path: string, content: string): void {\n mkdirSync(dirname(path), { recursive: true })\n // Include a random suffix so two concurrent writes don't trample each\n // other's tmp file. Web Crypto getRandomValues is non-blocking +\n // collision-safe for the tiny entropy we need (8 hex chars = 32 bits).\n // Hex-encoded manually to avoid Buffer (runtime-agnostic per ADR-0028).\n const randBuf = new Uint8Array(4)\n globalThis.crypto.getRandomValues(randBuf)\n let suffix = ''\n for (const b of randBuf) suffix += b.toString(16).padStart(2, '0')\n const tmp = `${path}.tmp-${process.pid}-${suffix}`\n writeFileSync(tmp, content, 'utf8')\n renameSync(tmp, path)\n}\n"],"mappings":";AASA,SAAS,WAAW,YAAY,qBAAqB;AACrD,SAAS,eAAe;AAkBjB,SAAS,YAAY,MAAc,SAAuB;AAC/D,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAK5C,QAAM,UAAU,IAAI,WAAW,CAAC;AAChC,aAAW,OAAO,gBAAgB,OAAO;AACzC,MAAI,SAAS;AACb,aAAW,KAAK,QAAS,WAAU,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACjE,QAAM,MAAM,GAAG,IAAI,QAAQ,QAAQ,GAAG,IAAI,MAAM;AAChD,gBAAc,KAAK,SAAS,MAAM;AAClC,aAAW,KAAK,IAAI;AACtB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/scan/middleware-scan.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/middleware/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync, existsSync, statSync } from 'node:fs'\nimport { join, extname } from 'node:path'\n\nconst MW_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n/**\n * Scan the server/middleware/ directory for middleware files.\n * Files are returned sorted alphabetically — use numeric prefixes\n * (e.g. 01-cors.ts, 02-auth.ts) to control execution order.\n *\n * Files starting with '_' or '.' are ignored (helpers, hidden files).\n */\nexport function scanMiddlewares(serverDir: string): string[] {\n const mwDir = join(serverDir, 'middleware')\n if (!existsSync(mwDir) || !statSync(mwDir).isDirectory()) {\n return []\n }\n\n const entries = readdirSync(mwDir, { withFileTypes: true })\n const files: string[] = []\n\n for (const entry of entries) {\n if (!entry.isFile()) continue\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n const ext = extname(entry.name)\n if (!MW_EXTENSIONS.has(ext)) continue\n files.push(join(mwDir, entry.name))\n }\n\n // Sort alphabetically — numeric prefix (01-, 02-) guarantees order.\n // Use `localeCompare` for stable, locale-aware ordering.\n files.sort((a, b) => a.localeCompare(b))\n return files\n}\n"],"mappings":";AAIA,SAAS,aAAa,YAAY,gBAAgB;AAClD,SAAS,MAAM,eAAe;AAE9B,IAAM,gBAAgB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,MAAM,CAAC;AASrD,SAAS,gBAAgB,WAA6B;AAC3D,QAAM,QAAQ,KAAK,WAAW,YAAY;AAC1C,MAAI,CAAC,WAAW,KAAK,KAAK,CAAC,SAAS,KAAK,EAAE,YAAY,GAAG;AACxD,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAU,YAAY,OAAO,EAAE,eAAe,KAAK,CAAC;AAC1D,QAAM,QAAkB,CAAC;AAEzB,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,OAAO,EAAG;AACrB,QAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,KAAK,WAAW,GAAG,EAAG;AAC9D,UAAM,MAAM,QAAQ,MAAM,IAAI;AAC9B,QAAI,CAAC,cAAc,IAAI,GAAG,EAAG;AAC7B,UAAM,KAAK,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,EACpC;AAIA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACvC,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/http/cookies.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport interface CookieOptions {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n maxAge?: number\n path?: string\n domain?: string\n}\n\n/**\n * Parse a `Cookie` header into a Map (RFC 6265 §5.4).\n * - Empty/missing input → empty Map.\n * - Malformed entries (no `=`) skipped silently (defensive).\n * - Duplicate names → last-wins.\n * - Values URL-decoded if possible; raw on decode failure (CR-009 protection).\n *\n * Canonical helper (T3.2 of architecture-review-remediation-plan; PV-4 DRY).\n * Consumed by `getCookie` here AND by `rate-limit/rate-limit-per-route.ts`.\n */\nexport function parseCookieHeader(header: string | undefined): Map<string, string> {\n const map = new Map<string, string>()\n if (!header) return map\n for (const pair of header.split(';')) {\n const trimmed = pair.trim()\n const eqIdx = trimmed.indexOf('=')\n if (eqIdx === -1) continue\n const key = trimmed.slice(0, eqIdx)\n const rawValue = trimmed.slice(eqIdx + 1)\n let value: string\n try {\n value = decodeURIComponent(rawValue)\n } catch {\n // Malformed percent-encoding — preserve raw to not lose data,\n // but the consumer is responsible for treating it as untrusted.\n value = rawValue\n }\n map.set(key, value)\n }\n return map\n}\n\nexport function getCookie(req: IncomingMessage, name: string): string | undefined {\n const cookies = parseCookieHeader(req.headers.cookie)\n const value = cookies.get(name)\n // CR-009 fix: malformed percent-encoding returns raw via parseCookieHeader.\n // If decoding failed in parse, we get the raw value (with %XX intact).\n // Treat that as \"unreadable\" for the typed getCookie API to preserve the\n // original \"treat as unauthenticated\" semantics.\n if (value === undefined) return undefined\n // Sanity: if rawValue contains lone `%` that didn't decode, parseCookieHeader\n // returned the raw — return undefined to preserve old getCookie contract.\n if (/(?:%[^0-9A-Fa-f])|(?:%[0-9A-Fa-f]$)/.test(value)) return undefined\n return value\n}\n\n/**\n * T5a.2 Phase B slice 6/6 — pure Set-Cookie serializer. Extracted from\n * `setCookie(res, ...)` so the IncomingMessage path AND the Web Headers\n * path can share the cookie attribute composition.\n *\n * Returns the canonical `Set-Cookie` header value string (no surrounding\n * `Set-Cookie:` prefix — caller wraps via `res.setHeader('Set-Cookie', ...)`\n * or `headers.append('Set-Cookie', ...)`).\n *\n * Defaults: `httpOnly: true`, `secure: NODE_ENV === 'production'`,\n * `sameSite: 'lax'`, `path: '/'`.\n */\nexport function serializeCookie(name: string, value: string, options?: CookieOptions): string {\n const opts: Required<Omit<CookieOptions, 'maxAge' | 'domain'>> &\n Pick<CookieOptions, 'maxAge' | 'domain'> = {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n path: '/',\n ...options,\n }\n\n const parts = [`${name}=${encodeURIComponent(value)}`]\n if (opts.httpOnly) parts.push('HttpOnly')\n if (opts.secure) parts.push('Secure')\n const sameSiteCanonical = opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1)\n parts.push(`SameSite=${sameSiteCanonical}`)\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n if (opts.path) parts.push(`Path=${opts.path}`)\n if (opts.domain) parts.push(`Domain=${opts.domain}`)\n return parts.join('; ')\n}\n\nexport function setCookie(\n res: ServerResponse,\n name: string,\n value: string,\n options?: CookieOptions,\n): void {\n const serialized = serializeCookie(name, value, options)\n // Append to existing Set-Cookie headers (EC-1: don't overwrite)\n const existing = res.getHeader('Set-Cookie')\n let cookies: string[] = []\n if (Array.isArray(existing)) {\n cookies = existing.map(String)\n } else if (existing !== undefined) {\n cookies = [String(existing)]\n }\n cookies.push(serialized)\n res.setHeader('Set-Cookie', cookies)\n}\n\nexport function deleteCookie(res: ServerResponse, name: string, options?: { path?: string }): void {\n setCookie(res, name, '', { maxAge: 0, path: options?.path ?? '/' })\n}\n\n/**\n * T5a.2 Phase B slice 6/6 — Web-Standards cookie getter.\n *\n * Mirror of `getCookie(req: IncomingMessage, name)` for the Web `Request`\n * shape. Consumes `request.headers.get('cookie')` (native `Headers` API)\n * instead of `req.headers.cookie`. Same parse logic + same malformed\n * percent-encoding sanity (returns undefined when raw value contains a\n * lone `%` that didn't decode, preserving the IncomingMessage path's\n * \"treat as unreadable\" CR-009 semantics).\n */\nexport function getCookieFromRequest(request: Request, name: string): string | undefined {\n const cookieHeader = request.headers.get('cookie')\n if (cookieHeader === null) return undefined\n const cookies = parseCookieHeader(cookieHeader)\n const value = cookies.get(name)\n if (value === undefined) return undefined\n if (/(?:%[^0-9A-Fa-f])|(?:%[0-9A-Fa-f]$)/.test(value)) return undefined\n return value\n}\n\n/**\n * T5a.2 Phase B slice 6/6 — Web-Standards cookie setter.\n *\n * Mirror of `setCookie(res, name, value, options)` for the Web `Headers`\n * shape. Appends a `Set-Cookie` entry to the caller's `Headers` instance\n * via `headers.append('Set-Cookie', ...)`. Multiple appends produce\n * multiple `Set-Cookie` headers per the Web spec; `headers.getSetCookie()`\n * retrieves them as an array (the one multi-value header the Web API\n * exposes natively).\n *\n * Uses the shared `serializeCookie` pure helper — same defaults and\n * attribute composition as the IncomingMessage path.\n */\nexport function appendCookieToHeaders(\n target: Headers,\n name: string,\n value: string,\n options?: CookieOptions,\n): void {\n target.append('Set-Cookie', serializeCookie(name, value, options))\n}\n\n/**\n * T5a.2 Phase B slice 6/6 — Web-Standards cookie deleter.\n *\n * Mirror of `deleteCookie(res, name, options)` — emits a Set-Cookie with\n * `Max-Age=0` so the browser drops the cookie immediately.\n */\nexport function appendDeleteCookieToHeaders(\n target: Headers,\n name: string,\n options?: { path?: string },\n): void {\n appendCookieToHeaders(target, name, '', { maxAge: 0, path: options?.path ?? '/' })\n}\n"],"mappings":";AAqBO,SAAS,kBAAkB,QAAiD;AACjF,QAAM,MAAM,oBAAI,IAAoB;AACpC,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,UAAU,GAAI;AAClB,UAAM,MAAM,QAAQ,MAAM,GAAG,KAAK;AAClC,UAAM,WAAW,QAAQ,MAAM,QAAQ,CAAC;AACxC,QAAI;AACJ,QAAI;AACF,cAAQ,mBAAmB,QAAQ;AAAA,IACrC,QAAQ;AAGN,cAAQ;AAAA,IACV;AACA,QAAI,IAAI,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAEO,SAAS,UAAU,KAAsB,MAAkC;AAChF,QAAM,UAAU,kBAAkB,IAAI,QAAQ,MAAM;AACpD,QAAM,QAAQ,QAAQ,IAAI,IAAI;AAK9B,MAAI,UAAU,OAAW,QAAO;AAGhC,MAAI,sCAAsC,KAAK,KAAK,EAAG,QAAO;AAC9D,SAAO;AACT;AAcO,SAAS,gBAAgB,MAAc,OAAe,SAAiC;AAC5F,QAAM,OACuC;AAAA,IAC3C,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,GAAG;AAAA,EACL;AAEA,QAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC,EAAE;AACrD,MAAI,KAAK,SAAU,OAAM,KAAK,UAAU;AACxC,MAAI,KAAK,OAAQ,OAAM,KAAK,QAAQ;AACpC,QAAM,oBAAoB,KAAK,SAAS,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,SAAS,MAAM,CAAC;AACvF,QAAM,KAAK,YAAY,iBAAiB,EAAE;AAC1C,MAAI,KAAK,WAAW,OAAW,OAAM,KAAK,WAAW,KAAK,MAAM,EAAE;AAClE,MAAI,KAAK,KAAM,OAAM,KAAK,QAAQ,KAAK,IAAI,EAAE;AAC7C,MAAI,KAAK,OAAQ,OAAM,KAAK,UAAU,KAAK,MAAM,EAAE;AACnD,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,UACd,KACA,MACA,OACA,SACM;AACN,QAAM,aAAa,gBAAgB,MAAM,OAAO,OAAO;AAEvD,QAAM,WAAW,IAAI,UAAU,YAAY;AAC3C,MAAI,UAAoB,CAAC;AACzB,MAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,cAAU,SAAS,IAAI,MAAM;AAAA,EAC/B,WAAW,aAAa,QAAW;AACjC,cAAU,CAAC,OAAO,QAAQ,CAAC;AAAA,EAC7B;AACA,UAAQ,KAAK,UAAU;AACvB,MAAI,UAAU,cAAc,OAAO;AACrC;AAEO,SAAS,aAAa,KAAqB,MAAc,SAAmC;AACjG,YAAU,KAAK,MAAM,IAAI,EAAE,QAAQ,GAAG,MAAM,SAAS,QAAQ,IAAI,CAAC;AACpE;AAYO,SAAS,qBAAqB,SAAkB,MAAkC;AACvF,QAAM,eAAe,QAAQ,QAAQ,IAAI,QAAQ;AACjD,MAAI,iBAAiB,KAAM,QAAO;AAClC,QAAM,UAAU,kBAAkB,YAAY;AAC9C,QAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,sCAAsC,KAAK,KAAK,EAAG,QAAO;AAC9D,SAAO;AACT;AAeO,SAAS,sBACd,QACA,MACA,OACA,SACM;AACN,SAAO,OAAO,cAAc,gBAAgB,MAAM,OAAO,OAAO,CAAC;AACnE;AAQO,SAAS,4BACd,QACA,MACA,SACM;AACN,wBAAsB,QAAQ,MAAM,IAAI,EAAE,QAAQ,GAAG,MAAM,SAAS,QAAQ,IAAI,CAAC;AACnF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vite-plugin/controller-swc-transform.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport { transformControllerSource, type SwcCore } from '@theokit/http'\nimport type { Plugin } from 'vite'\n\n/** Vite normalizes module ids to POSIX `/` on every OS; normalize the prefix to match. */\nfunction toPosix(p: string): string {\n return p.replace(/\\\\/g, '/')\n}\n\n/**\n * Vite plugin that compiles `<serverDir>/controllers/**` through @theokit/http's\n * swc transform so parameter decorators (`@Body`, `@Param`, `@Query`) emit the\n * `design:paramtypes` metadata esbuild silently drops. Without it, controller\n * dispatch (Task 1.2) cannot validate `@Body` or bind `@Param`.\n *\n * Scope discipline (ADR-4): the transform is a strict no-op for every file\n * outside `controllers/`, so file-based `route()` compilation is byte-for-byte\n * unchanged. `enforce: 'pre'` runs it before esbuild would mangle the decorators.\n *\n * The swc-loader is REUSED, never reimplemented (ADR-1 / Rule 9 / G12): the swc\n * option block lives once in `@theokit/http`.\n */\ninterface ControllerSwcTransformOptions {\n /** Absolute backend root (config `serverDir`). Only `controllers/` under it is transformed. */\n serverDir: string\n /**\n * Injectable `@swc/core` loader — test seam only. Defaults to the loader\n * inside `transformControllerSource`, which resolves + caches @swc/core.\n */\n loadSwcCore?: () => Promise<SwcCore | null>\n}\n\nexport function controllerSwcTransformPlugin(options: ControllerSwcTransformOptions): Plugin {\n // Trailing `/` so `server/controllers-ui/` cannot false-match `server/controllers`.\n // POSIX-normalized so the prefix compares against Vite's `/`-separated ids on Windows too.\n const controllersDir = toPosix(resolve(options.serverDir, 'controllers')) + '/'\n\n return {\n name: 'theo:controller-swc-transform',\n enforce: 'pre',\n async transform(code: string, id: string) {\n // Strip Vite's query suffix (`?t=...`, `?worker`, etc.) + normalize separators.\n const cleanId = toPosix(id.split('?')[0])\n if (!cleanId.startsWith(controllersDir) || !cleanId.endsWith('.ts')) {\n return null\n }\n const transformed = await transformControllerSource(code, cleanId, options.loadSwcCore)\n return { code: transformed, map: null }\n },\n }\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;AAExB,SAAS,iCAA+C;AAIxD,SAAS,QAAQ,GAAmB;AAClC,SAAO,EAAE,QAAQ,OAAO,GAAG;AAC7B;AAyBO,SAAS,6BAA6B,SAAgD;AAG3F,QAAM,iBAAiB,QAAQ,QAAQ,QAAQ,WAAW,aAAa,CAAC,IAAI;AAE5E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM,UAAU,MAAc,IAAY;AAExC,YAAM,UAAU,QAAQ,GAAG,MAAM,GAAG,EAAE,CAAC,CAAC;AACxC,UAAI,CAAC,QAAQ,WAAW,cAAc,KAAK,CAAC,QAAQ,SAAS,KAAK,GAAG;AACnE,eAAO;AAAA,MACT;AACA,YAAM,cAAc,MAAM,0BAA0B,MAAM,SAAS,QAAQ,WAAW;AACtF,aAAO,EAAE,MAAM,aAAa,KAAK,KAAK;AAAA,IACxC;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vite-plugin/openapi-emit/emit.ts","../src/vite-plugin/openapi-emit/zod-to-openapi.ts","../src/vite-plugin/openapi-emit/load-routes.ts","../src/vite-plugin/openapi-emit/dev-emit.ts"],"sourcesContent":["/**\n * In-house `emitOpenApi()` orchestrator (build-time only).\n *\n * Pure transformation: takes a route manifest (already hydrated with Zod\n * schemas extracted from `defineRoute()` config) + the openapi config\n * block, builds an OpenAPI 3.x document, writes `<outDir>/openapi.json`,\n * returns the document for downstream callers (CLI logs, tests).\n *\n * Per G2 plan v1.1 T2.1. Modelled on trpc-openapi's\n * `generateOpenApiDocument` + encore's `Generator.Generate()`.\n *\n * NEVER ship to runtime — devDep-shaped artifact.\n *\n * Path templating: TheoKit's `:param` syntax is converted to OpenAPI's\n * `{param}` per the spec. Env-var override: `THEOKIT_OPENAPI_SERVER_URL`\n * overrides `servers[0].url` without rebuilding the config.\n */\n/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time emit. outDir is a config-controlled relative path. The\n * caller already refused absolute paths via theoConfigSchema.distDir\n * pattern; the openapi.outDir field follows the same convention.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { z } from 'zod'\n\nimport { zodToOpenApiSchema, type ConvertCtx, type OpenApiSchema } from './zod-to-openapi.js'\n\nexport interface OpenApiManifestRoute {\n /** TheoKit-style path with `:param` placeholders (`/users/:id`). */\n routePath: string\n /** Uppercase HTTP methods the route exports (`['GET','POST']`). */\n methods: string[]\n /** Optional `defineRoute({body})` schema. */\n body?: z.ZodType\n /** Optional `defineRoute({query})` schema. */\n query?: z.ZodType\n /** Optional `defineRoute({params})` schema. */\n params?: z.ZodType\n /** Optional `defineRoute({response})` schema. */\n response?: z.ZodType\n}\n\nexport interface OpenApiEmitConfig {\n servers: { url: string; description?: string }[]\n specVersion: '3.1.0' | '3.0.3'\n title: string\n version: string\n outDir: string\n}\n\ninterface OpenApiOperation {\n operationId?: string\n parameters?: OpenApiParameter[]\n requestBody?: {\n required?: boolean\n content: Record<string, { schema: OpenApiSchema }>\n }\n responses: Record<\n string,\n { description: string; content?: Record<string, { schema: OpenApiSchema }> }\n >\n}\n\ninterface OpenApiParameter {\n name: string\n in: 'path' | 'query' | 'header' | 'cookie'\n required: boolean\n schema: OpenApiSchema\n description?: string\n}\n\ntype OpenApiPathItem = Partial<\n Record<'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options', OpenApiOperation>\n>\n\ninterface OpenApiDocument {\n openapi: '3.1.0' | '3.0.3'\n info: { title: string; version: string }\n servers: { url: string; description?: string }[]\n paths: Record<string, OpenApiPathItem>\n components?: { schemas?: Record<string, OpenApiSchema> }\n}\n\ninterface EmitOpenApiInput {\n manifest: OpenApiManifestRoute[]\n config: OpenApiEmitConfig\n}\n\ninterface EmitOpenApiResult {\n document: OpenApiDocument\n path: string\n}\n\nconst METHOD_TO_KEY: Record<string, keyof OpenApiPathItem> = {\n GET: 'get',\n POST: 'post',\n PUT: 'put',\n PATCH: 'patch',\n DELETE: 'delete',\n HEAD: 'head',\n OPTIONS: 'options',\n}\n\n/** Convert `/users/:id/posts/:postId?` → `/users/{id}/posts/{postId}`. */\nfunction templatePath(routePath: string): string {\n // Bounded quantifier (\\w{0,64}) is OSS-safe — TheoKit route param names\n // never exceed 64 chars in practice; cap prevents super-linear backtracking.\n return routePath.replace(/:([A-Za-z_]\\w{0,64})\\??/g, '{$1}')\n}\n\nfunction buildParameters(\n paramsSchema: z.ZodType | undefined,\n querySchema: z.ZodType | undefined,\n ctx: ConvertCtx,\n): OpenApiParameter[] {\n const out: OpenApiParameter[] = []\n if (paramsSchema !== undefined) {\n appendObjectAsParameters(paramsSchema, 'path', out, ctx)\n }\n if (querySchema !== undefined) {\n appendObjectAsParameters(querySchema, 'query', out, ctx)\n }\n return out\n}\n\nfunction appendObjectAsParameters(\n schema: z.ZodType,\n location: 'path' | 'query',\n out: OpenApiParameter[],\n ctx: ConvertCtx,\n): void {\n // We expect an object schema; if it's not, skip silently (caller already\n // validated upstream when scanning defineRoute()).\n const shape = (schema as unknown as { shape?: Record<string, z.ZodType> }).shape\n if (shape === undefined) return\n for (const [name, child] of Object.entries(shape)) {\n // A field is optional iff the schema accepts `undefined` (covers\n // `.optional()` + `.default()`). Runtime check via safeParse — robust\n // across zod versions, unlike reading the changed `_def.typeName` internal\n // (zod 3) / `def.type` (zod 4).\n const optional = child.safeParse(undefined).success\n out.push({\n name,\n in: location,\n required: location === 'path' ? true : !optional,\n schema: zodToOpenApiSchema(child, { ctx }),\n })\n }\n}\n\nfunction buildOperation(\n route: OpenApiManifestRoute,\n ctx: ConvertCtx,\n method: string,\n): OpenApiOperation {\n // Bounded sanitizer: two single-pass replacements (no nested quantifiers).\n const sanitized = route.routePath.replace(/[/:\\-{}]/g, '_').replace(/_+/g, '_')\n const operationId = `${method.toLowerCase()}_${sanitized.replace(/^_|_$/g, '')}`\n const op: OpenApiOperation = {\n operationId,\n responses: {\n '200': { description: 'OK' },\n },\n }\n\n const parameters = buildParameters(route.params, route.query, ctx)\n if (parameters.length > 0) op.parameters = parameters\n\n // Body only meaningful for write methods; we still attach if defined so\n // the document is honest about the contract.\n if (route.body !== undefined) {\n op.requestBody = {\n required: true,\n content: {\n 'application/json': { schema: zodToOpenApiSchema(route.body, { ctx }) },\n },\n }\n }\n\n if (route.response !== undefined) {\n op.responses['200'] = {\n description: 'OK',\n content: {\n 'application/json': { schema: zodToOpenApiSchema(route.response, { ctx }) },\n },\n }\n }\n\n return op\n}\n\nexport function emitOpenApi(input: EmitOpenApiInput): EmitOpenApiResult {\n const { manifest, config } = input\n\n const ctx: ConvertCtx = { seen: new Map(), components: {} }\n\n const paths: Record<string, OpenApiPathItem> = {}\n for (const route of manifest) {\n const templatedPath = templatePath(route.routePath)\n const item: OpenApiPathItem = paths[templatedPath] ?? {}\n for (const method of route.methods) {\n // Defensive: manifest emitter normalizes to uppercase, but if a\n // future scanner produces a non-HTTP verb the key lookup yields\n // undefined and we skip silently. TS narrows the indexed type to\n // keyof OpenApiPathItem; the runtime undefined check is honest.\n const key = METHOD_TO_KEY[method.toUpperCase()] as keyof OpenApiPathItem | undefined\n if (key === undefined) continue\n item[key] = buildOperation(route, ctx, method)\n }\n paths[templatedPath] = item\n }\n\n const servers = applyServerOverride(config.servers)\n\n const document: OpenApiDocument = {\n openapi: config.specVersion,\n info: { title: config.title, version: config.version },\n servers,\n paths,\n }\n\n if (Object.keys(ctx.components).length > 0) {\n document.components = { schemas: ctx.components }\n }\n\n mkdirSync(config.outDir, { recursive: true })\n const outPath = join(config.outDir, 'openapi.json')\n writeFileSync(outPath, `${JSON.stringify(document, null, 2)}\\n`, 'utf8')\n\n return { document, path: outPath }\n}\n\nfunction applyServerOverride(\n servers: { url: string; description?: string }[],\n): { url: string; description?: string }[] {\n const override = process.env.THEOKIT_OPENAPI_SERVER_URL\n if (override === undefined || override === '') return servers\n if (servers.length === 0) return [{ url: override }]\n const [first, ...rest] = servers\n return [\n {\n url: override,\n ...(first.description !== undefined ? { description: first.description } : {}),\n },\n ...rest,\n ]\n}\n","/**\n * Zod → OpenAPI 3.0 Schema converter (build-time only).\n *\n * Uses Zod v4's native `z.toJSONSchema()` and post-processes\n * the JSON Schema output into OpenAPI 3.0 format:\n * - Removes `$schema` (invalid in OpenAPI 3.0)\n * - Converts `type: [\"string\", \"null\"]` → `{ type: \"string\", nullable: true }`\n * - Recursively normalizes nested schemas\n *\n * NEVER ship to runtime — devDep-shaped artifact.\n */\nimport { z } from 'zod'\n\nexport interface OpenApiSchema {\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null'\n format?: string\n items?: OpenApiSchema\n properties?: Record<string, OpenApiSchema>\n required?: string[]\n additionalProperties?: boolean | OpenApiSchema\n oneOf?: OpenApiSchema[]\n anyOf?: OpenApiSchema[]\n allOf?: OpenApiSchema[]\n discriminator?: { propertyName: string; mapping?: Record<string, string> }\n enum?: unknown[]\n nullable?: boolean\n description?: string\n default?: unknown\n $ref?: string\n}\n\nexport interface ConvertCtx {\n seen: Map<z.ZodType, string>\n components: Record<string, OpenApiSchema>\n}\n\ninterface ConvertOptions {\n ctx?: ConvertCtx\n name?: string\n}\n\nexport class ZodToOpenApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ZodToOpenApiError'\n }\n}\n\nexport function zodToOpenApiSchema(schema: z.ZodType, options: ConvertOptions = {}): OpenApiSchema {\n const ctx: ConvertCtx = options.ctx ?? { seen: new Map(), components: {} }\n\n const name = options.name\n if (name !== undefined) {\n if (Object.prototype.hasOwnProperty.call(ctx.components, name)) {\n return { $ref: `#/components/schemas/${name}` }\n }\n ctx.seen.set(schema, name)\n ctx.components[name] = {}\n const body = convertSchema(schema, ctx)\n ctx.components[name] = body\n return { $ref: `#/components/schemas/${name}` }\n }\n\n return convertSchema(schema, ctx)\n}\n\ninterface ZodInternalDef {\n type?: string\n discriminator?: string\n in?: z.ZodType\n}\n\n/** Read zod v4's internal schema def (the `.def` getter) without a hard `any`. */\nfunction zodDef(schema: z.ZodType): ZodInternalDef | undefined {\n return (schema as unknown as { def?: ZodInternalDef }).def\n}\n\nfunction convertSchema(schema: z.ZodType, ctx: ConvertCtx): OpenApiSchema {\n const existing = ctx.seen.get(schema)\n if (existing !== undefined) {\n return { $ref: `#/components/schemas/${existing}` }\n }\n\n const def = zodDef(schema)\n // Fail loud on schema kinds that have no OpenAPI representation (rather than\n // swallowing the zod throw into `{}`). z.function() is the canonical case.\n if (def?.type === 'function') {\n throw new ZodToOpenApiError(\n 'z.function() cannot be represented in an OpenAPI schema (functions are not serializable).',\n )\n }\n\n // A transform / pipe (`z.string().transform(...)`) has no JSON-Schema of its\n // own — zod throws. The wire shape is the INPUT (transforms run post-parse),\n // so emit the input schema. This avoids the global `io: 'input'` mode, which\n // would also flip object `additionalProperties` semantics.\n if (def?.type === 'pipe' && def.in !== undefined) {\n return convertSchema(def.in, ctx)\n }\n\n try {\n const jsonSchema = z.toJSONSchema(schema) as Record<string, unknown>\n const out = toOpenApi3(jsonSchema)\n // zod v4 emits a discriminated union as `oneOf` but drops the discriminator\n // key; re-attach it from the zod def so OpenAPI consumers can branch.\n if (def?.type === 'union' && def.discriminator !== undefined && out.oneOf !== undefined) {\n out.discriminator = { propertyName: def.discriminator }\n }\n return out\n } catch (err) {\n if (err instanceof ZodToOpenApiError) throw err\n return {}\n }\n}\n\n/** JS safe-integer bounds zod v4 attaches to `.int()` — noise for OpenAPI, stripped. */\nconst SAFE_INT_MIN = Number.MIN_SAFE_INTEGER\nconst SAFE_INT_MAX = Number.MAX_SAFE_INTEGER\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\n/** Drop zod-v4 noise keys: redundant `pattern` when `format` is set; JS safe-integer sentinel bounds. */\nfunction isNoiseKey(key: string, value: unknown, isInteger: boolean, hasFormat: boolean): boolean {\n if (key === 'pattern' && hasFormat) return true\n if (isInteger && key === 'minimum' && value === SAFE_INT_MIN) return true\n if (isInteger && key === 'maximum' && value === SAFE_INT_MAX) return true\n return false\n}\n\n/** Recursively normalize one JSON-Schema value (properties/items/*Of/additionalProperties). */\nfunction normalizeValue(key: string, value: unknown): unknown {\n if (key === 'properties' && isRecord(value)) {\n const props: Record<string, unknown> = {}\n for (const [pk, pv] of Object.entries(value)) props[pk] = isRecord(pv) ? toOpenApi3(pv) : pv\n return props\n }\n if (key === 'items' && isRecord(value)) return toOpenApi3(value)\n if ((key === 'anyOf' || key === 'oneOf' || key === 'allOf') && Array.isArray(value)) {\n const arr: unknown[] = value\n return arr.map((v) => (isRecord(v) ? toOpenApi3(v) : v))\n }\n if (key === 'additionalProperties' && isRecord(value)) return toOpenApi3(value)\n return value\n}\n\n/** Convert zod v4's `anyOf` (used for `.nullable()` + `z.union()`) to OpenAPI 3.0 shape. */\nfunction convertAnyOf(branches: unknown[], rest: Record<string, unknown>): OpenApiSchema {\n const records = branches.filter(isRecord)\n const hasNull = records.some((b) => b.type === 'null')\n const nonNull = records.filter((b) => b.type !== 'null')\n if (hasNull && nonNull.length === 1) {\n return { ...toOpenApi3(nonNull[0]), nullable: true }\n }\n const result: OpenApiSchema = { oneOf: nonNull.map((b) => toOpenApi3(b)) }\n if (hasNull) result.nullable = true\n for (const [k, v] of Object.entries(rest))\n if (k !== 'anyOf') (result as Record<string, unknown>)[k] = v\n return result\n}\n\n/**\n * Post-process JSON Schema (draft-2020-12) → OpenAPI 3.0:\n * - strip `$schema`/`$defs`\n * - `.nullable()`/union `anyOf` → `nullable: true` / `oneOf`\n * - `type: [\"string\",\"null\"]` → `{ type: \"string\", nullable: true }`\n * - drop zod-v4 noise (redundant pattern, safe-integer bounds)\n * - recurse into nested schemas\n */\nfunction toOpenApi3(js: Record<string, unknown>): OpenApiSchema {\n const { $schema: _, $defs: _defs, ...rest } = js\n if (Array.isArray(rest.anyOf)) return convertAnyOf(rest.anyOf, rest)\n\n const isInteger = rest.type === 'integer'\n const hasFormat = typeof rest.format === 'string'\n const out: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (isNoiseKey(key, value, isInteger, hasFormat)) continue\n // `const` is JSON-Schema / OpenAPI-3.1 only; `enum: [x]` is the 3.0-and-3.1\n // compatible spelling. zod v4 emits `const` for literals (discriminator tags).\n if (key === 'const') {\n out.enum = [value]\n continue\n }\n if (key === 'type' && Array.isArray(value)) {\n const types = value.filter((t): t is string => typeof t === 'string')\n const nonNull = types.filter((t) => t !== 'null')\n out.type = nonNull.length === 1 ? nonNull[0] : nonNull\n if (types.includes('null')) out.nullable = true\n } else {\n out[key] = normalizeValue(key, value)\n }\n }\n\n return out\n}\n","/**\n * Build-time route loader for OpenAPI emit.\n *\n * Takes the G1 manifest (filePath + routePath + methods) and hydrates each\n * route's Zod schemas by dynamically importing the module. `defineRoute`\n * is an identity function, so the imported config exposes `body/query/\n * params/response` directly without any AST gymnastics.\n *\n * Supports both export shapes:\n * 1. `export const POST = defineRoute({...})` (per-method named exports)\n * 2. `export default defineRoute({...})` (single-export legacy shape)\n *\n * Per G2 plan v1.1 T2.2. Uses Vite's `ssrLoadModule` because route files\n * are TS at build time — Node's native loader doesn't transpile.\n */\nimport { resolve } from 'node:path'\n\nimport type { z } from 'zod'\n\nimport type { ManifestRoute } from '../../server/scan/manifest.js'\n\nimport type { OpenApiManifestRoute } from './emit.js'\n\ninterface RouteConfigShape {\n body?: z.ZodType\n query?: z.ZodType\n params?: z.ZodType\n response?: z.ZodType\n}\n\ninterface ViteLikeServer {\n ssrLoadModule: (id: string) => Promise<Record<string, unknown>>\n close: () => Promise<void>\n}\n\ninterface DefaultLoader {\n load: (absPath: string) => Promise<Record<string, unknown>>\n dispose: () => Promise<void>\n}\n\n/** Method names recognized as per-route named exports (UPPERCASE). */\nconst HTTP_METHOD_KEYS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\n\ninterface LoadRoutesOptions {\n serverDir: string\n routes: ManifestRoute[]\n /** Injectable for tests; defaults to a fresh Vite SSR dev server. */\n loadModule?: (absPath: string) => Promise<Record<string, unknown>>\n}\n\n/**\n * Hydrate manifest routes with Zod schemas extracted from their modules.\n *\n * Returns one `OpenApiManifestRoute` per (file × method) tuple. If a route\n * file declares multiple methods (`GET` + `POST`), each method gets its\n * own entry with the schemas from that method's `defineRoute()` call.\n *\n * Routes that fail to load are skipped with a console warning — the emit\n * should be best-effort, not a hard build failure.\n *\n * The default Vite loader is created once per call and disposed in the\n * `finally` block so the underlying server is released even when route\n * loading throws.\n */\nexport async function loadRoutesForOpenApi(\n options: LoadRoutesOptions,\n): Promise<OpenApiManifestRoute[]> {\n const { serverDir, routes } = options\n let loadModule = options.loadModule\n let dispose: (() => Promise<void>) | undefined\n if (loadModule === undefined) {\n const created = await createDefaultLoader(serverDir)\n loadModule = created.load\n dispose = created.dispose\n }\n\n const out: OpenApiManifestRoute[] = []\n try {\n for (const r of routes) {\n const abs = resolve(serverDir, r.filePath)\n let mod: Record<string, unknown>\n try {\n mod = await loadModule(abs)\n } catch (err) {\n console.warn(\n `[openapi-emit] Skipping ${r.filePath}: load failed (${(err as Error).message})`,\n )\n continue\n }\n\n const methods = r.methods ?? HTTP_METHOD_KEYS.filter((m) => mod[m] !== undefined)\n if (methods.length === 0) {\n // Fall back to default export when no method-named exports detected.\n const def = mod.default\n if (def !== undefined && def !== null) {\n out.push({ routePath: r.routePath, methods: ['GET'], ...extractSchemas(def) })\n }\n continue\n }\n\n for (const method of methods) {\n const handler = mod[method] ?? mod.default\n if (handler === undefined || handler === null) continue\n out.push({ routePath: r.routePath, methods: [method], ...extractSchemas(handler) })\n }\n }\n } finally {\n if (dispose !== undefined) {\n await dispose()\n }\n }\n return out\n}\n\nfunction extractSchemas(handlerExport: unknown): RouteConfigShape {\n if (typeof handlerExport !== 'object' || handlerExport === null) return {}\n const cfg = handlerExport as RouteConfigShape\n const out: RouteConfigShape = {}\n if (cfg.body !== undefined) out.body = cfg.body\n if (cfg.query !== undefined) out.query = cfg.query\n if (cfg.params !== undefined) out.params = cfg.params\n if (cfg.response !== undefined) out.response = cfg.response\n return out\n}\n\n/**\n * Default loader: spin up a minimal Vite SSR dev server (no HMR, no\n * middlewares) just to use its `ssrLoadModule`. Vite handles TS, ESM, and\n * import resolution for free.\n *\n * Returns `{load, dispose}` — the caller owns the lifecycle and MUST\n * call `dispose()` (typically in a `finally`) so the server is closed.\n */\nasync function createDefaultLoader(serverDir: string): Promise<DefaultLoader> {\n // Vite is a peerDep in build flows; import dynamically.\n const { createServer } = await import('vite')\n const server = (await createServer({\n root: serverDir,\n configFile: false,\n server: { middlewareMode: true },\n appType: 'custom',\n logLevel: 'silent',\n })) as unknown as ViteLikeServer\n\n const swallow = (): void => {\n /* eat close errors — best-effort cleanup */\n }\n return {\n load: async (absPath) => server.ssrLoadModule(absPath),\n dispose: async () => {\n await server.close().catch(swallow)\n },\n }\n}\n","/**\n * Dev-mode OpenAPI emit helper for `theokit dev`.\n *\n * Per P#3 plan v1.3 T1.1 (ADRs D3 + D4 + EC-8). Called once on `theokit dev`\n * boot AND on every chokidar 'change'/'add'/'unlink' watcher event for\n * route files when `config.openapi !== undefined`. Re-runs G2's\n * `emitOpenApi()` orchestrator to keep `<distDir>/openapi.json` always\n * fresh in dev.\n *\n * EC-8 absorbed (single-flight guard): if a previous invocation is still\n * awaiting `loadRoutesForOpenApi` (Vite SSR loader can hang on circular\n * imports or syntax errors), skip the new call and emit a `console.warn`\n * so watcher events don't pile up + dev server doesn't deadlock.\n *\n * Best-effort: ALL errors caught + warned. Never throws out of the\n * exported function (would crash Vite dev's chokidar handler).\n *\n * NEVER ship to runtime — dev-only artifact gated on `config.openapi`.\n */\nimport { generateManifest } from '../../server/scan/manifest.js'\n\nimport { emitOpenApi } from './emit.js'\nimport { loadRoutesForOpenApi } from './load-routes.js'\n\ninterface DevEmitConfig {\n servers: { url: string; description?: string }[]\n specVersion: '3.1.0' | '3.0.3'\n title: string\n version: string\n}\n\nlet inFlight = false\n\nexport async function reEmitOpenApi(\n serverDir: string,\n distDir: string,\n openApiConfig: DevEmitConfig,\n): Promise<void> {\n if (inFlight) {\n console.warn(\n '[openapi-emit] previous emit still running; skipping watcher event (EC-8 single-flight guard)',\n )\n return\n }\n inFlight = true\n try {\n const manifest = generateManifest(serverDir)\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n emitOpenApi({\n manifest: hydrated,\n config: { ...openApiConfig, outDir: distDir },\n })\n } catch (err) {\n console.warn(`[openapi-emit] re-emit failed: ${(err as Error).message}`)\n } finally {\n inFlight = false\n }\n}\n\n/** Test seam — resets the inFlight flag between tests. NOT for production use. */\nexport function _resetInFlightForTests(): void {\n inFlight = false\n}\n"],"mappings":"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","names":[]}
@@ -1,8 +0,0 @@
1
- import {
2
- dispatcher
3
- } from "./chunk-XZ6B2Y2R.js";
4
- import "./chunk-DGUM43GV.js";
5
- export {
6
- dispatcher
7
- };
8
- //# sourceMappingURL=dispatcher-DLKIXYNO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}