theokit 0.43.12 → 0.44.3

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 (302) 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-IK2SDIZ3.js} +4 -3
  4. package/dist/{actions-virtual-module-HG66SZ7T.js.map → actions-virtual-module-IK2SDIZ3.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-OCCL5RUY.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-GDF2OMJ2.js → app-typed-client-4OH3JREB.js} +47 -8
  13. package/dist/app-typed-client-4OH3JREB.js.map +1 -0
  14. package/dist/{app-typed-client-USLAMN7X.js → app-typed-client-N5WUE2CP.js} +62 -24
  15. package/dist/app-typed-client-N5WUE2CP.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-25RSFP22.js} +13 -4
  23. package/dist/build-25RSFP22.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-JQJN6LFK.js → chunk-62EU2BVZ.js} +2 -2
  41. package/dist/chunk-62EU2BVZ.js.map +1 -0
  42. package/dist/{chunk-SJIRP73B.js → chunk-646CA6RV.js} +2 -2
  43. package/dist/chunk-646CA6RV.js.map +1 -0
  44. package/dist/{chunk-ASDXVRJD.js → chunk-6KDXP472.js} +4 -4
  45. package/dist/chunk-6KDXP472.js.map +1 -0
  46. package/dist/chunk-6LNIGF5P.js +46 -0
  47. package/dist/chunk-6LNIGF5P.js.map +1 -0
  48. package/dist/chunk-7IHWXDJJ.js +1 -0
  49. package/dist/chunk-7IHWXDJJ.js.map +1 -0
  50. package/dist/{chunk-JQSKBMXP.js → chunk-7LVM22VG.js} +1 -1
  51. package/dist/chunk-7LVM22VG.js.map +1 -0
  52. package/dist/{chunk-UVXB2ER7.js → chunk-7SABX4DJ.js} +1 -1
  53. package/dist/chunk-7SABX4DJ.js.map +1 -0
  54. package/dist/{chunk-X32XEJXR.js → chunk-ACKBVBUI.js} +1 -1
  55. package/dist/chunk-ACKBVBUI.js.map +1 -0
  56. package/dist/{chunk-UUFYP2UM.js → chunk-AM3QABW5.js} +3 -3
  57. package/dist/chunk-AM3QABW5.js.map +1 -0
  58. package/dist/{chunk-FH3DYBAN.js → chunk-ANS4ELRH.js} +1 -1
  59. package/dist/chunk-ANS4ELRH.js.map +1 -0
  60. package/dist/{chunk-UD3LFGDL.js → chunk-AVQU5CQF.js} +1 -1
  61. package/dist/chunk-AVQU5CQF.js.map +1 -0
  62. package/dist/{chunk-CJNMJUHE.js → chunk-B7ONQ4QJ.js} +2 -2
  63. package/dist/{chunk-WSJKACWB.js → chunk-C34PMLSN.js} +1 -1
  64. package/dist/chunk-C34PMLSN.js.map +1 -0
  65. package/dist/{chunk-2WH6SD3C.js → chunk-DSOBIXXR.js} +4 -4
  66. package/dist/{chunk-DJFMAWH5.js → chunk-EIRQM55C.js} +6 -5
  67. package/dist/chunk-EIRQM55C.js.map +1 -0
  68. package/dist/{chunk-ZEGYW52B.js → chunk-FB2HBNIC.js} +1 -1
  69. package/dist/chunk-FB2HBNIC.js.map +1 -0
  70. package/dist/chunk-FXZZS7YW.js +605 -0
  71. package/dist/chunk-FXZZS7YW.js.map +1 -0
  72. package/dist/{chunk-HGUZWG2C.js → chunk-FZG3JYB3.js} +8 -47
  73. package/dist/chunk-FZG3JYB3.js.map +1 -0
  74. package/dist/{chunk-V3LJN2GS.js → chunk-GAPBPRPP.js} +1 -1
  75. package/dist/chunk-GAPBPRPP.js.map +1 -0
  76. package/dist/chunk-GUBN5UTT.js +1 -0
  77. package/dist/chunk-GUBN5UTT.js.map +1 -0
  78. package/dist/{chunk-6OXCZMW3.js → chunk-HEK4SET7.js} +5 -5
  79. package/dist/chunk-HEK4SET7.js.map +1 -0
  80. package/dist/{chunk-N2D5AVRR.js → chunk-HRCNHWLP.js} +7 -109
  81. package/dist/chunk-HRCNHWLP.js.map +1 -0
  82. package/dist/{chunk-C3ZZ56YZ.js → chunk-JA2ZAT5Q.js} +1 -1
  83. package/dist/chunk-JA2ZAT5Q.js.map +1 -0
  84. package/dist/{chunk-L2S2R3TI.js → chunk-KBGGQK4I.js} +25 -8
  85. package/dist/chunk-KBGGQK4I.js.map +1 -0
  86. package/dist/{chunk-VPISAMXU.js → chunk-KD5G3YXI.js} +4 -4
  87. package/dist/chunk-KD5G3YXI.js.map +1 -0
  88. package/dist/{chunk-VMEWD57H.js → chunk-KPKLIUUL.js} +1 -1
  89. package/dist/chunk-KPKLIUUL.js.map +1 -0
  90. package/dist/{chunk-4HAU7AJM.js → chunk-L4Y6RJ4E.js} +3 -3
  91. package/dist/chunk-L4Y6RJ4E.js.map +1 -0
  92. package/dist/{chunk-OVHW23YC.js → chunk-L5Z6IY5U.js} +5 -5
  93. package/dist/chunk-L5Z6IY5U.js.map +1 -0
  94. package/dist/{chunk-JBCHWRKF.js → chunk-MTEWLGVY.js} +2 -2
  95. package/dist/chunk-MTEWLGVY.js.map +1 -0
  96. package/dist/{chunk-XZ6B2Y2R.js → chunk-MWP4YXWJ.js} +1 -1
  97. package/dist/chunk-MWP4YXWJ.js.map +1 -0
  98. package/dist/{chunk-575PAX5L.js → chunk-NGEBYCOZ.js} +62 -38
  99. package/dist/chunk-NGEBYCOZ.js.map +1 -0
  100. package/dist/{chunk-5QW7IQQU.js → chunk-NTDN54XO.js} +1 -1
  101. package/dist/chunk-NTDN54XO.js.map +1 -0
  102. package/dist/{chunk-7BIM27LV.js → chunk-OIGJOYX6.js} +1 -1
  103. package/dist/chunk-OIGJOYX6.js.map +1 -0
  104. package/dist/{chunk-TSLSIRBR.js → chunk-OLGPYOLO.js} +2 -2
  105. package/dist/chunk-OLGPYOLO.js.map +1 -0
  106. package/dist/{chunk-IE6C4KZX.js → chunk-Q5OT7LZG.js} +11 -496
  107. package/dist/chunk-Q5OT7LZG.js.map +1 -0
  108. package/dist/{chunk-6NEXVBPY.js → chunk-RWNSW4FM.js} +1 -1
  109. package/dist/chunk-RWNSW4FM.js.map +1 -0
  110. package/dist/{chunk-E25FBYJP.js → chunk-SAFGDIPQ.js} +1 -1
  111. package/dist/chunk-SAFGDIPQ.js.map +1 -0
  112. package/dist/{chunk-OP7QCTR5.js → chunk-SEELKQUM.js} +2 -2
  113. package/dist/chunk-SEELKQUM.js.map +1 -0
  114. package/dist/{chunk-4B2ZMX45.js → chunk-SSYDEKWF.js} +1 -1
  115. package/dist/chunk-SSYDEKWF.js.map +1 -0
  116. package/dist/{chunk-DQVBL2G4.js → chunk-SZU6BOMV.js} +75 -53
  117. package/dist/chunk-SZU6BOMV.js.map +1 -0
  118. package/dist/{chunk-65EFIUDD.js → chunk-U267AOWS.js} +1 -1
  119. package/dist/chunk-U267AOWS.js.map +1 -0
  120. package/dist/{chunk-FX72VHAE.js → chunk-U3H53IIN.js} +6 -6
  121. package/dist/chunk-U3H53IIN.js.map +1 -0
  122. package/dist/{chunk-OPLICDUB.js → chunk-UW2TEAGR.js} +1 -1
  123. package/dist/chunk-UW2TEAGR.js.map +1 -0
  124. package/dist/{chunk-IAJ2JVEH.js → chunk-VSLTKX5L.js} +1 -1
  125. package/dist/chunk-VSLTKX5L.js.map +1 -0
  126. package/dist/{chunk-YB5DJJ4U.js → chunk-VZTYCAO6.js} +1 -1
  127. package/dist/chunk-VZTYCAO6.js.map +1 -0
  128. package/dist/chunk-WDGYCBNI.js +65 -0
  129. package/dist/chunk-WDGYCBNI.js.map +1 -0
  130. package/dist/chunk-WPJOTQA5.js +1 -0
  131. package/dist/chunk-WPJOTQA5.js.map +1 -0
  132. package/dist/{chunk-DGUM43GV.js → chunk-XTHN6JBX.js} +1 -1
  133. package/dist/chunk-XTHN6JBX.js.map +1 -0
  134. package/dist/{chunk-HZ6USUHC.js → chunk-YOIVCCZT.js} +1 -1
  135. package/dist/chunk-YOIVCCZT.js.map +1 -0
  136. package/dist/{chunk-OI6DK6ZJ.js → chunk-ZC2UESE4.js} +6 -4
  137. package/dist/{chunk-OI6DK6ZJ.js.map → chunk-ZC2UESE4.js.map} +1 -1
  138. package/dist/cli/index.js +8 -8
  139. package/dist/client/core.d.ts +4 -258
  140. package/dist/client/core.js +13 -9
  141. package/dist/client/core.js.map +1 -1
  142. package/dist/client/index.d.ts +7 -96
  143. package/dist/client/index.js +19 -67
  144. package/dist/client/index.js.map +1 -1
  145. package/dist/{controller-swc-transform-JJRMTSDY.js → controller-swc-transform-T7ZTD55V.js} +2 -2
  146. package/dist/controller-swc-transform-T7ZTD55V.js.map +1 -0
  147. package/dist/{cookies-MJKMGJ7N.js → cookies-SOXUQJXG.js} +3 -3
  148. package/dist/cookies-SOXUQJXG.js.map +1 -0
  149. package/dist/{dev-WQ4LWCAW.js → dev-V6UBM2OL.js} +8 -7
  150. package/dist/{dev-WQ4LWCAW.js.map → dev-V6UBM2OL.js.map} +1 -1
  151. package/dist/{dev-emit-HLGJK3PP.js → dev-emit-F54Y5TIU.js} +5 -5
  152. package/dist/dev-emit-F54Y5TIU.js.map +1 -0
  153. package/dist/devtools/entry.js +61 -53
  154. package/dist/devtools/entry.js.map +1 -1
  155. package/dist/dispatcher-MXKF3CRH.js +8 -0
  156. package/dist/dispatcher-MXKF3CRH.js.map +1 -0
  157. package/dist/index.js +25 -25
  158. package/dist/index.js.map +1 -1
  159. package/dist/{info-75U6GCMD.js → info-ICDTPJDC.js} +3 -3
  160. package/dist/{internal-api-JFORKYLR.js → internal-api-ALXVBL56.js} +10 -8
  161. package/dist/{internal-api-DV3H5NNV.js → internal-api-S7WK2WCY.js} +20 -20
  162. package/dist/internal-api-S7WK2WCY.js.map +1 -0
  163. package/dist/{load-config-XP7NSSA7.js → load-config-ICCNMO74.js} +3 -3
  164. package/dist/{mcp-FF3NTCHB.js → mcp-4JFODA5U.js} +3 -3
  165. package/dist/{openapi-ZPXYRAM2.js → openapi-NIYA2VBG.js} +3 -3
  166. package/dist/react-query/index.js +2 -2
  167. package/dist/react-query/index.js.map +1 -1
  168. package/dist/{routes-ZVDKABXZ.js → routes-2RD4S5PZ.js} +3 -3
  169. package/dist/{schema-FBOTN46F.js → schema-S553QCAU.js} +2 -2
  170. package/dist/server/agent/index.d.ts +3 -72
  171. package/dist/server/agent/index.js +4 -4
  172. package/dist/server/agent/index.js.map +1 -1
  173. package/dist/server/auth/index.js +4 -4
  174. package/dist/server/auth/index.js.map +1 -1
  175. package/dist/server/cost/index.js +2 -2
  176. package/dist/server/cost/index.js.map +1 -1
  177. package/dist/server/cron/index.js +6 -6
  178. package/dist/server/cron/index.js.map +1 -1
  179. package/dist/server/define/index.js +4 -4
  180. package/dist/server/define/index.js.map +1 -1
  181. package/dist/server/http/index.js +11 -11
  182. package/dist/server/http/index.js.map +1 -1
  183. package/dist/server/index.d.ts +2 -2
  184. package/dist/server/index.js +41 -41
  185. package/dist/server/index.js.map +1 -1
  186. package/dist/server/jobs/index.js +7 -7
  187. package/dist/server/jobs/index.js.map +1 -1
  188. package/dist/server/observability/index.d.ts +8 -1
  189. package/dist/server/observability/index.js +5 -5
  190. package/dist/server/observability/index.js.map +1 -1
  191. package/dist/server/plugins/index.js +3 -3
  192. package/dist/server/plugins/index.js.map +1 -1
  193. package/dist/server/rate-limit/index.js +4 -4
  194. package/dist/server/rate-limit/index.js.map +1 -1
  195. package/dist/server/realtime/index.js +2 -2
  196. package/dist/server/realtime/index.js.map +1 -1
  197. package/dist/server/scan/index.js +7 -7
  198. package/dist/server/scan/index.js.map +1 -1
  199. package/dist/server/security/index.js +5 -5
  200. package/dist/server/security/index.js.map +1 -1
  201. package/dist/server/storage/index.js +2 -2
  202. package/dist/server/storage/index.js.map +1 -1
  203. package/dist/server/webhook/index.js +3 -3
  204. package/dist/server/webhook/index.js.map +1 -1
  205. package/dist/server-error-to-envelope-UM7DEVM4.js +8 -0
  206. package/dist/server-error-to-envelope-UM7DEVM4.js.map +1 -0
  207. package/dist/{server-routes-hmr-RI7SYNT2.js → server-routes-hmr-6CRNSEWF.js} +2 -2
  208. package/dist/server-routes-hmr-6CRNSEWF.js.map +1 -0
  209. package/dist/{services-typed-client-AU6DKXAO.js → services-typed-client-IXNOPVZA.js} +3 -3
  210. package/dist/services-typed-client-IXNOPVZA.js.map +1 -0
  211. package/dist/{start-2U3FUCJX.js → start-2LNPPG74.js} +35 -11
  212. package/dist/start-2LNPPG74.js.map +1 -0
  213. package/dist/vite-plugin/index.js +25 -25
  214. package/dist/vite-plugin/index.js.map +1 -1
  215. package/dist/{vite-plugin-D2NTPZNC.js → vite-plugin-X6YO3OUH.js} +8 -7
  216. package/package.json +9 -5
  217. package/dist/actions-virtual-module-RSFRPJQZ.js.map +0 -1
  218. package/dist/agents-typed-client-ZP7BKJB3.js.map +0 -1
  219. package/dist/app-typed-client-GDF2OMJ2.js.map +0 -1
  220. package/dist/app-typed-client-USLAMN7X.js.map +0 -1
  221. package/dist/body-parser-web-NNEJGX6Y.js.map +0 -1
  222. package/dist/build-MQHIJROR.js.map +0 -1
  223. package/dist/build-request-body-preview-FVBQ7LCE.js.map +0 -1
  224. package/dist/chunk-26ECIX74.js.map +0 -1
  225. package/dist/chunk-4B2ZMX45.js.map +0 -1
  226. package/dist/chunk-4HAU7AJM.js.map +0 -1
  227. package/dist/chunk-575PAX5L.js.map +0 -1
  228. package/dist/chunk-5QW7IQQU.js.map +0 -1
  229. package/dist/chunk-65EFIUDD.js.map +0 -1
  230. package/dist/chunk-6FK7FKY6.js.map +0 -1
  231. package/dist/chunk-6NEXVBPY.js.map +0 -1
  232. package/dist/chunk-6OXCZMW3.js.map +0 -1
  233. package/dist/chunk-7BIM27LV.js.map +0 -1
  234. package/dist/chunk-7MQOHNHE.js.map +0 -1
  235. package/dist/chunk-ASDXVRJD.js.map +0 -1
  236. package/dist/chunk-C3ZZ56YZ.js.map +0 -1
  237. package/dist/chunk-DJFMAWH5.js.map +0 -1
  238. package/dist/chunk-DQVBL2G4.js.map +0 -1
  239. package/dist/chunk-E25FBYJP.js.map +0 -1
  240. package/dist/chunk-E3JH6YUM.js +0 -1
  241. package/dist/chunk-ESC54TAK.js.map +0 -1
  242. package/dist/chunk-FH3DYBAN.js.map +0 -1
  243. package/dist/chunk-FX72VHAE.js.map +0 -1
  244. package/dist/chunk-HGUZWG2C.js.map +0 -1
  245. package/dist/chunk-HZ6USUHC.js.map +0 -1
  246. package/dist/chunk-IAJ2JVEH.js.map +0 -1
  247. package/dist/chunk-IE6C4KZX.js.map +0 -1
  248. package/dist/chunk-JBCHWRKF.js.map +0 -1
  249. package/dist/chunk-JHEAWR3L.js +0 -1
  250. package/dist/chunk-JIL74SFK.js.map +0 -1
  251. package/dist/chunk-JQJN6LFK.js.map +0 -1
  252. package/dist/chunk-JQSKBMXP.js.map +0 -1
  253. package/dist/chunk-KPRJS3II.js +0 -505
  254. package/dist/chunk-KPRJS3II.js.map +0 -1
  255. package/dist/chunk-L2S2R3TI.js.map +0 -1
  256. package/dist/chunk-N2D5AVRR.js.map +0 -1
  257. package/dist/chunk-NM4WF3XD.js.map +0 -1
  258. package/dist/chunk-NPMCKOX4.js +0 -1
  259. package/dist/chunk-NPMCKOX4.js.map +0 -1
  260. package/dist/chunk-OP7QCTR5.js.map +0 -1
  261. package/dist/chunk-OPLICDUB.js.map +0 -1
  262. package/dist/chunk-OVHW23YC.js.map +0 -1
  263. package/dist/chunk-SJIRP73B.js.map +0 -1
  264. package/dist/chunk-TSLSIRBR.js.map +0 -1
  265. package/dist/chunk-UD3LFGDL.js.map +0 -1
  266. package/dist/chunk-UUFYP2UM.js.map +0 -1
  267. package/dist/chunk-UVXB2ER7.js.map +0 -1
  268. package/dist/chunk-V3LJN2GS.js.map +0 -1
  269. package/dist/chunk-VMEWD57H.js.map +0 -1
  270. package/dist/chunk-VPISAMXU.js.map +0 -1
  271. package/dist/chunk-WSJKACWB.js.map +0 -1
  272. package/dist/chunk-X32XEJXR.js.map +0 -1
  273. package/dist/chunk-XZ6B2Y2R.js.map +0 -1
  274. package/dist/chunk-YB5DJJ4U.js.map +0 -1
  275. package/dist/chunk-ZEGYW52B.js.map +0 -1
  276. package/dist/chunk-ZJQ4O76G.js.map +0 -1
  277. package/dist/controller-swc-transform-JJRMTSDY.js.map +0 -1
  278. package/dist/cookies-MJKMGJ7N.js.map +0 -1
  279. package/dist/dev-emit-HLGJK3PP.js.map +0 -1
  280. package/dist/dispatcher-DLKIXYNO.js +0 -8
  281. package/dist/dispatcher-DLKIXYNO.js.map +0 -1
  282. package/dist/internal-api-DV3H5NNV.js.map +0 -1
  283. package/dist/internal-api-JFORKYLR.js.map +0 -1
  284. package/dist/load-config-XP7NSSA7.js.map +0 -1
  285. package/dist/schema-FBOTN46F.js.map +0 -1
  286. package/dist/server-error-to-envelope-EOFUIUNB.js +0 -8
  287. package/dist/server-error-to-envelope-EOFUIUNB.js.map +0 -1
  288. package/dist/server-routes-hmr-RI7SYNT2.js.map +0 -1
  289. package/dist/services-typed-client-AU6DKXAO.js.map +0 -1
  290. package/dist/start-2U3FUCJX.js.map +0 -1
  291. package/dist/vite-plugin-D2NTPZNC.js.map +0 -1
  292. /package/dist/{agent-OXRRKB55.js.map → agent-OCCL5RUY.js.map} +0 -0
  293. /package/dist/{chunk-CJNMJUHE.js.map → chunk-B7ONQ4QJ.js.map} +0 -0
  294. /package/dist/{chunk-2WH6SD3C.js.map → chunk-DSOBIXXR.js.map} +0 -0
  295. /package/dist/{info-75U6GCMD.js.map → info-ICDTPJDC.js.map} +0 -0
  296. /package/dist/{broadcast-4SX7R5XE.js.map → internal-api-ALXVBL56.js.map} +0 -0
  297. /package/dist/{chunk-DGUM43GV.js.map → load-config-ICCNMO74.js.map} +0 -0
  298. /package/dist/{mcp-FF3NTCHB.js.map → mcp-4JFODA5U.js.map} +0 -0
  299. /package/dist/{openapi-ZPXYRAM2.js.map → openapi-NIYA2VBG.js.map} +0 -0
  300. /package/dist/{routes-ZVDKABXZ.js.map → routes-2RD4S5PZ.js.map} +0 -0
  301. /package/dist/{chunk-E3JH6YUM.js.map → schema-S553QCAU.js.map} +0 -0
  302. /package/dist/{chunk-JHEAWR3L.js.map → vite-plugin-X6YO3OUH.js.map} +0 -0
@@ -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":";AAkDO,IAAM,gBAAN,MAAM,eAAwC;AAAA,EAC3C,QAAQ,oBAAI,IAA4B;AAAA;AAAA,EAEhD,OAAgB,cAAc;AAAA;AAAA,EAE9B,OAAgB,iBAAiB;AAAA,EAEzB,UAAiD;AAAA,EAEzD,cAAc;AAKZ,QAAI,OAAO,gBAAgB,aAAa;AACtC,YAAM,QAAQ,YAAY,MAAM;AAC9B,aAAK,aAAa;AAAA,MACpB,GAAG,eAAc,cAAc;AAC/B,WAAK,UAAU;AAGf,YAAM,aAAc,MAAiC;AACrD,UAAI,OAAO,eAAe,WAAY,YAAW,KAAK,KAAK;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,KAAa,UAAkC;AACtD,QAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAAG;AAC/C,YAAM,IAAI;AAAA,QACR,sEAAsE,QAAQ;AAAA,MAChF;AAAA,IACF;AACA,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,KAAK,MAAM,QAAQ,eAAc,aAAa;AAChD,YAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,KAAK,EAAE;AACvC,UAAI,UAAU,OAAW,MAAK,MAAM,OAAO,KAAK;AAAA,IAClD;AAEA,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,SAAS,OAAO,MAAM,SAAS;AAClC,YAAM,QAAQ,EAAE,OAAO,GAAG,SAAS,MAAM,SAAS;AAClD,WAAK,MAAM,IAAI,KAAK,KAAK;AACzB,aAAO,EAAE,GAAG,MAAM;AAAA,IACpB;AACA,UAAM;AACN,WAAO,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ;AAAA,EACtD;AAAA;AAAA,EAGA,eAAqB;AACnB,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,GAAG,CAAC,KAAK,KAAK,OAAO;AAC/B,UAAI,EAAE,WAAW,IAAK,MAAK,MAAM,OAAO,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,QAAI,KAAK,SAAS;AAChB,oBAAc,KAAK,OAAO;AAC1B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAK,KAAa,UAA2C;AACjE,WAAO,KAAK,SAAS,KAAK,QAAQ;AAAA,EACpC;AAAA;AAAA,EAGA,MAAM,IAAI,KAA6C;AACrD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,OAAO,MAAM,QAAS,QAAO;AACjC,WAAO,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ;AAAA,EACtD;AAAA;AAAA,EAGA,MAAM,MAAM,KAA4B;AACtC,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AACF;;;ACjHO,SAAS,kBAAkB,QAAyB,OAAmC,CAAC,GAAG;AAChG,QAAM,QAAQ,KAAK,SAAS,IAAI,cAAc;AAC9C,QAAM,aAAa,iBAAiB;AAEpC,SAAO,SAAS,eAAe,KAAuC;AAIpE,UAAM,MAAM,IAAI,QAAQ,iBAAiB;AAEzC,QAAI,YAAY;AAEd,YAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,QAAQ;AACjD,aAAO,gBAAgB,OAAO,MAAM;AAAA,IACtC;AAMA,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;AAEA,SAAS,gBACP,OACA,QACiB;AACjB,MAAI,MAAM,QAAQ,OAAO,KAAK;AAC5B,UAAM,aAAa,KAAK,MAAM,MAAM,UAAU,KAAK,IAAI,KAAK,GAAI;AAChE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,QACP,qBAAqB,OAAO,OAAO,GAAG;AAAA,QACtC,yBAAyB;AAAA,QACzB,eAAe,OAAO,UAAU;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,MACP,qBAAqB,OAAO,OAAO,GAAG;AAAA,MACtC,yBAAyB,OAAO,KAAK,IAAI,GAAG,OAAO,MAAM,MAAM,KAAK,CAAC;AAAA,IACvE;AAAA,EACF;AACF;AAuBO,SAAS,qBACd,QACA,OAAmC,CAAC,GACpC;AACA,QAAM,QAAQ,KAAK,SAAS,IAAI,cAAc;AAC9C,QAAM,aAAa,iBAAiB;AAEpC,SAAO,SAAS,kBAAkB,UAAmC;AACnE,UAAM,MAAM,SAAS,SAAS,IAAI,WAAW;AAC7C,QAAI,YAAY;AACd,YAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,QAAQ;AACjD,aAAO,gBAAgB,OAAO,MAAM;AAAA,IACtC;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;","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":";;;;;;;;AAsBA,SAAS,WAAW,qBAAqB;AACzC,SAAS,YAAY;;;ACZrB,SAAS,SAAS;AA8BX,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,mBAAmB,QAAmB,UAA0B,CAAC,GAAkB;AACjG,QAAM,MAAkB,QAAQ,OAAO,EAAE,MAAM,oBAAI,IAAI,GAAG,YAAY,CAAC,EAAE;AAEzE,QAAM,OAAO,QAAQ;AACrB,MAAI,SAAS,QAAW;AACtB,QAAI,OAAO,UAAU,eAAe,KAAK,IAAI,YAAY,IAAI,GAAG;AAC9D,aAAO,EAAE,MAAM,wBAAwB,IAAI,GAAG;AAAA,IAChD;AACA,QAAI,KAAK,IAAI,QAAQ,IAAI;AACzB,QAAI,WAAW,IAAI,IAAI,CAAC;AACxB,UAAM,OAAO,cAAc,QAAQ,GAAG;AACtC,QAAI,WAAW,IAAI,IAAI;AACvB,WAAO,EAAE,MAAM,wBAAwB,IAAI,GAAG;AAAA,EAChD;AAEA,SAAO,cAAc,QAAQ,GAAG;AAClC;AASA,SAAS,OAAO,QAA+C;AAC7D,SAAQ,OAA+C;AACzD;AAEA,SAAS,cAAc,QAAmB,KAAgC;AACxE,QAAM,WAAW,IAAI,KAAK,IAAI,MAAM;AACpC,MAAI,aAAa,QAAW;AAC1B,WAAO,EAAE,MAAM,wBAAwB,QAAQ,GAAG;AAAA,EACpD;AAEA,QAAM,MAAM,OAAO,MAAM;AAGzB,MAAI,KAAK,SAAS,YAAY;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAMA,MAAI,KAAK,SAAS,UAAU,IAAI,OAAO,QAAW;AAChD,WAAO,cAAc,IAAI,IAAI,GAAG;AAAA,EAClC;AAEA,MAAI;AACF,UAAM,aAAa,EAAE,aAAa,MAAM;AACxC,UAAM,MAAM,WAAW,UAAU;AAGjC,QAAI,KAAK,SAAS,WAAW,IAAI,kBAAkB,UAAa,IAAI,UAAU,QAAW;AACvF,UAAI,gBAAgB,EAAE,cAAc,IAAI,cAAc;AAAA,IACxD;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,eAAe,kBAAmB,OAAM;AAC5C,WAAO,CAAC;AAAA,EACV;AACF;AAGA,IAAM,eAAe,OAAO;AAC5B,IAAM,eAAe,OAAO;AAE5B,SAAS,SAAS,GAA0C;AAC1D,SAAO,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAChE;AAGA,SAAS,WAAW,KAAa,OAAgB,WAAoB,WAA6B;AAChG,MAAI,QAAQ,aAAa,UAAW,QAAO;AAC3C,MAAI,aAAa,QAAQ,aAAa,UAAU,aAAc,QAAO;AACrE,MAAI,aAAa,QAAQ,aAAa,UAAU,aAAc,QAAO;AACrE,SAAO;AACT;AAGA,SAAS,eAAe,KAAa,OAAyB;AAC5D,MAAI,QAAQ,gBAAgB,SAAS,KAAK,GAAG;AAC3C,UAAM,QAAiC,CAAC;AACxC,eAAW,CAAC,IAAI,EAAE,KAAK,OAAO,QAAQ,KAAK,EAAG,OAAM,EAAE,IAAI,SAAS,EAAE,IAAI,WAAW,EAAE,IAAI;AAC1F,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,WAAW,SAAS,KAAK,EAAG,QAAO,WAAW,KAAK;AAC/D,OAAK,QAAQ,WAAW,QAAQ,WAAW,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG;AACnF,UAAM,MAAiB;AACvB,WAAO,IAAI,IAAI,CAAC,MAAO,SAAS,CAAC,IAAI,WAAW,CAAC,IAAI,CAAE;AAAA,EACzD;AACA,MAAI,QAAQ,0BAA0B,SAAS,KAAK,EAAG,QAAO,WAAW,KAAK;AAC9E,SAAO;AACT;AAGA,SAAS,aAAa,UAAqB,MAA8C;AACvF,QAAM,UAAU,SAAS,OAAO,QAAQ;AACxC,QAAM,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACrD,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AACvD,MAAI,WAAW,QAAQ,WAAW,GAAG;AACnC,WAAO,EAAE,GAAG,WAAW,QAAQ,CAAC,CAAC,GAAG,UAAU,KAAK;AAAA,EACrD;AACA,QAAM,SAAwB,EAAE,OAAO,QAAQ,IAAI,CAAC,MAAM,WAAW,CAAC,CAAC,EAAE;AACzE,MAAI,QAAS,QAAO,WAAW;AAC/B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI;AACtC,QAAI,MAAM,QAAS,CAAC,OAAmC,CAAC,IAAI;AAC9D,SAAO;AACT;AAUA,SAAS,WAAW,IAA4C;AAC9D,QAAM,EAAE,SAAS,GAAG,OAAO,OAAO,GAAG,KAAK,IAAI;AAC9C,MAAI,MAAM,QAAQ,KAAK,KAAK,EAAG,QAAO,aAAa,KAAK,OAAO,IAAI;AAEnE,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,YAAY,OAAO,KAAK,WAAW;AACzC,QAAM,MAA+B,CAAC;AAEtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,QAAI,WAAW,KAAK,OAAO,WAAW,SAAS,EAAG;AAGlD,QAAI,QAAQ,SAAS;AACnB,UAAI,OAAO,CAAC,KAAK;AACjB;AAAA,IACF;AACA,QAAI,QAAQ,UAAU,MAAM,QAAQ,KAAK,GAAG;AAC1C,YAAM,QAAQ,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AACpE,YAAM,UAAU,MAAM,OAAO,CAAC,MAAM,MAAM,MAAM;AAChD,UAAI,OAAO,QAAQ,WAAW,IAAI,QAAQ,CAAC,IAAI;AAC/C,UAAI,MAAM,SAAS,MAAM,EAAG,KAAI,WAAW;AAAA,IAC7C,OAAO;AACL,UAAI,GAAG,IAAI,eAAe,KAAK,KAAK;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AACT;;;ADtGA,IAAM,gBAAuD;AAAA,EAC3D,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAGA,SAAS,aAAa,WAA2B;AAG/C,SAAO,UAAU,QAAQ,4BAA4B,MAAM;AAC7D;AAEA,SAAS,gBACP,cACA,aACA,KACoB;AACpB,QAAM,MAA0B,CAAC;AACjC,MAAI,iBAAiB,QAAW;AAC9B,6BAAyB,cAAc,QAAQ,KAAK,GAAG;AAAA,EACzD;AACA,MAAI,gBAAgB,QAAW;AAC7B,6BAAyB,aAAa,SAAS,KAAK,GAAG;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,yBACP,QACA,UACA,KACA,KACM;AAGN,QAAM,QAAS,OAA4D;AAC3E,MAAI,UAAU,OAAW;AACzB,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAKjD,UAAM,WAAW,MAAM,UAAU,MAAS,EAAE;AAC5C,QAAI,KAAK;AAAA,MACP;AAAA,MACA,IAAI;AAAA,MACJ,UAAU,aAAa,SAAS,OAAO,CAAC;AAAA,MACxC,QAAQ,mBAAmB,OAAO,EAAE,IAAI,CAAC;AAAA,IAC3C,CAAC;AAAA,EACH;AACF;AAEA,SAAS,eACP,OACA,KACA,QACkB;AAElB,QAAM,YAAY,MAAM,UAAU,QAAQ,aAAa,GAAG,EAAE,QAAQ,OAAO,GAAG;AAC9E,QAAM,cAAc,GAAG,OAAO,YAAY,CAAC,IAAI,UAAU,QAAQ,UAAU,EAAE,CAAC;AAC9E,QAAM,KAAuB;AAAA,IAC3B;AAAA,IACA,WAAW;AAAA,MACT,OAAO,EAAE,aAAa,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,aAAa,gBAAgB,MAAM,QAAQ,MAAM,OAAO,GAAG;AACjE,MAAI,WAAW,SAAS,EAAG,IAAG,aAAa;AAI3C,MAAI,MAAM,SAAS,QAAW;AAC5B,OAAG,cAAc;AAAA,MACf,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB,EAAE,QAAQ,mBAAmB,MAAM,MAAM,EAAE,IAAI,CAAC,EAAE;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,aAAa,QAAW;AAChC,OAAG,UAAU,KAAK,IAAI;AAAA,MACpB,aAAa;AAAA,MACb,SAAS;AAAA,QACP,oBAAoB,EAAE,QAAQ,mBAAmB,MAAM,UAAU,EAAE,IAAI,CAAC,EAAE;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,YAAY,OAA4C;AACtE,QAAM,EAAE,UAAU,OAAO,IAAI;AAE7B,QAAM,MAAkB,EAAE,MAAM,oBAAI,IAAI,GAAG,YAAY,CAAC,EAAE;AAE1D,QAAM,QAAyC,CAAC;AAChD,aAAW,SAAS,UAAU;AAC5B,UAAM,gBAAgB,aAAa,MAAM,SAAS;AAClD,UAAM,OAAwB,MAAM,aAAa,KAAK,CAAC;AACvD,eAAW,UAAU,MAAM,SAAS;AAKlC,YAAM,MAAM,cAAc,OAAO,YAAY,CAAC;AAC9C,UAAI,QAAQ,OAAW;AACvB,WAAK,GAAG,IAAI,eAAe,OAAO,KAAK,MAAM;AAAA,IAC/C;AACA,UAAM,aAAa,IAAI;AAAA,EACzB;AAEA,QAAM,UAAU,oBAAoB,OAAO,OAAO;AAElD,QAAM,WAA4B;AAAA,IAChC,SAAS,OAAO;AAAA,IAChB,MAAM,EAAE,OAAO,OAAO,OAAO,SAAS,OAAO,QAAQ;AAAA,IACrD;AAAA,IACA;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,IAAI,UAAU,EAAE,SAAS,GAAG;AAC1C,aAAS,aAAa,EAAE,SAAS,IAAI,WAAW;AAAA,EAClD;AAEA,YAAU,OAAO,QAAQ,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,UAAU,KAAK,OAAO,QAAQ,cAAc;AAClD,gBAAc,SAAS,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AAEvE,SAAO,EAAE,UAAU,MAAM,QAAQ;AACnC;AAEA,SAAS,oBACP,SACyC;AACzC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,aAAa,UAAa,aAAa,GAAI,QAAO;AACtD,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC,EAAE,KAAK,SAAS,CAAC;AACnD,QAAM,CAAC,OAAO,GAAG,IAAI,IAAI;AACzB,SAAO;AAAA,IACL;AAAA,MACE,KAAK;AAAA,MACL,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,IAC9E;AAAA,IACA,GAAG;AAAA,EACL;AACF;;;AEzOA,SAAS,eAAe;AA0BxB,IAAM,mBAAmB,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAuBpF,eAAsB,qBACpB,SACiC;AACjC,QAAM,EAAE,WAAW,OAAO,IAAI;AAC9B,MAAI,aAAa,QAAQ;AACzB,MAAI;AACJ,MAAI,eAAe,QAAW;AAC5B,UAAM,UAAU,MAAM,oBAAoB,SAAS;AACnD,iBAAa,QAAQ;AACrB,cAAU,QAAQ;AAAA,EACpB;AAEA,QAAM,MAA8B,CAAC;AACrC,MAAI;AACF,eAAW,KAAK,QAAQ;AACtB,YAAM,MAAM,QAAQ,WAAW,EAAE,QAAQ;AACzC,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,WAAW,GAAG;AAAA,MAC5B,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACN,2BAA2B,EAAE,QAAQ,kBAAmB,IAAc,OAAO;AAAA,QAC/E;AACA;AAAA,MACF;AAEA,YAAM,UAAU,EAAE,WAAW,iBAAiB,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,MAAS;AAChF,UAAI,QAAQ,WAAW,GAAG;AAExB,cAAM,MAAM,IAAI;AAChB,YAAI,QAAQ,UAAa,QAAQ,MAAM;AACrC,cAAI,KAAK,EAAE,WAAW,EAAE,WAAW,SAAS,CAAC,KAAK,GAAG,GAAG,eAAe,GAAG,EAAE,CAAC;AAAA,QAC/E;AACA;AAAA,MACF;AAEA,iBAAW,UAAU,SAAS;AAC5B,cAAM,UAAU,IAAI,MAAM,KAAK,IAAI;AACnC,YAAI,YAAY,UAAa,YAAY,KAAM;AAC/C,YAAI,KAAK,EAAE,WAAW,EAAE,WAAW,SAAS,CAAC,MAAM,GAAG,GAAG,eAAe,OAAO,EAAE,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,EACF,UAAE;AACA,QAAI,YAAY,QAAW;AACzB,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,eAA0C;AAChE,MAAI,OAAO,kBAAkB,YAAY,kBAAkB,KAAM,QAAO,CAAC;AACzE,QAAM,MAAM;AACZ,QAAM,MAAwB,CAAC;AAC/B,MAAI,IAAI,SAAS,OAAW,KAAI,OAAO,IAAI;AAC3C,MAAI,IAAI,UAAU,OAAW,KAAI,QAAQ,IAAI;AAC7C,MAAI,IAAI,WAAW,OAAW,KAAI,SAAS,IAAI;AAC/C,MAAI,IAAI,aAAa,OAAW,KAAI,WAAW,IAAI;AACnD,SAAO;AACT;AAUA,eAAe,oBAAoB,WAA2C;AAE5E,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,MAAM;AAC5C,QAAM,SAAU,MAAM,aAAa;AAAA,IACjC,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,QAAQ,EAAE,gBAAgB,KAAK;AAAA,IAC/B,SAAS;AAAA,IACT,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,UAAU,MAAY;AAAA,EAE5B;AACA,SAAO;AAAA,IACL,MAAM,OAAO,YAAY,OAAO,cAAc,OAAO;AAAA,IACrD,SAAS,YAAY;AACnB,YAAM,OAAO,MAAM,EAAE,MAAM,OAAO;AAAA,IACpC;AAAA,EACF;AACF;;;AC1HA,IAAI,WAAW;AAEf,eAAsB,cACpB,WACA,SACA,eACe;AACf,MAAI,UAAU;AACZ,YAAQ;AAAA,MACN;AAAA,IACF;AACA;AAAA,EACF;AACA,aAAW;AACX,MAAI;AACF,UAAM,WAAW,iBAAiB,SAAS;AAC3C,UAAM,WAAW,MAAM,qBAAqB,EAAE,WAAW,QAAQ,SAAS,OAAO,CAAC;AAClF,gBAAY;AAAA,MACV,UAAU;AAAA,MACV,QAAQ,EAAE,GAAG,eAAe,QAAQ,QAAQ;AAAA,IAC9C,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,KAAK,kCAAmC,IAAc,OAAO,EAAE;AAAA,EACzE,UAAE;AACA,eAAW;AAAA,EACb;AACF;AAGO,SAAS,yBAA+B;AAC7C,aAAW;AACb;","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":[]}
@@ -1,8 +0,0 @@
1
- import {
2
- serverErrorToEnvelope
3
- } from "./chunk-UVXB2ER7.js";
4
- import "./chunk-DGUM43GV.js";
5
- export {
6
- serverErrorToEnvelope
7
- };
8
- //# sourceMappingURL=server-error-to-envelope-EOFUIUNB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vite-plugin/server-routes-hmr.ts"],"sourcesContent":["/**\n * G6 T1.2 — Vite plugin: watch `server/routes/**` and invalidate SSR module\n * cache on add/change/unlink with a 50 ms debounce window.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * Pattern reference: SvelteKit `exports/vite/dev/index.js:335-398`.\n *\n * Why this exists: in dev, `scanServerRoutes` runs per-request (fresh disk\n * read), so adding/removing routes already takes effect on next request.\n * But the SSR dynamic-import cache holds stale code for EDITED routes — Vite\n * needs an explicit `invalidateModule` call to bust it. We also send a\n * `full-reload` so any browser code that imported a route module\n * (rare, but the typed client G1 does this for ts-only type resolution)\n * picks up the change.\n *\n * EC-6: 50 ms debounce collapses bursty file events (e.g., codemod-driven\n * 21-rename storm in T2.1) into a single invalidation cycle — without it,\n * 21 invalidations + 21 full-reloads in 5 s would hang the dev server.\n */\nimport { sep } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\ninterface ServerRoutesHmrPluginOptions {\n /** Absolute path of the consumer's `server/` directory. */\n serverDir: string\n /** Debounce window in milliseconds (default 50 ms per EC-6). */\n debounceMs?: number\n}\n\ninterface ModuleGraphLike {\n getModuleById: (id: string) => unknown\n invalidateModule: (mod: unknown) => void\n idToModuleMap: Map<string, { id: string }>\n}\n\ninterface ViteDevServerLike {\n watcher: { on: (event: string, listener: (path: string) => void) => void }\n moduleGraph: ModuleGraphLike\n ws: { send: (payload: { type: string }) => void }\n}\n\nexport function serverRoutesHmrPlugin(opts: ServerRoutesHmrPluginOptions): Plugin {\n const routesDir = `${opts.serverDir}${sep}routes${sep}`\n // Cross-platform: on Windows the watcher MAY emit forward slashes even on NTFS.\n const routesDirPosix = routesDir.replace(/\\\\/g, '/')\n const debounceMs = opts.debounceMs ?? 50\n\n const isUnderRoutes = (filePath: string): boolean => {\n const normalized = filePath.replace(/\\\\/g, '/')\n return normalized.startsWith(routesDirPosix)\n }\n\n return {\n name: 'theokit:server-routes-hmr',\n\n configureServer(server: ViteDevServer) {\n const srv = server as unknown as ViteDevServerLike\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const fireInvalidation = (): void => {\n timer = null\n for (const mod of srv.moduleGraph.idToModuleMap.values()) {\n const normalizedId = mod.id.replace(/\\\\/g, '/')\n if (normalizedId.startsWith(routesDirPosix)) {\n srv.moduleGraph.invalidateModule(mod)\n }\n }\n srv.ws.send({ type: 'full-reload' })\n }\n\n const scheduleInvalidation = (filePath: string): void => {\n if (!isUnderRoutes(filePath)) return\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(fireInvalidation, debounceMs)\n }\n\n srv.watcher.on('add', scheduleInvalidation)\n srv.watcher.on('change', scheduleInvalidation)\n srv.watcher.on('unlink', scheduleInvalidation)\n },\n }\n}\n"],"mappings":";;;AAoBA,SAAS,WAAW;AAuBb,SAAS,sBAAsB,MAA4C;AAChF,QAAM,YAAY,GAAG,KAAK,SAAS,GAAG,GAAG,SAAS,GAAG;AAErD,QAAM,iBAAiB,UAAU,QAAQ,OAAO,GAAG;AACnD,QAAM,aAAa,KAAK,cAAc;AAEtC,QAAM,gBAAgB,CAAC,aAA8B;AACnD,UAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,WAAO,WAAW,WAAW,cAAc;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,gBAAgB,QAAuB;AACrC,YAAM,MAAM;AACZ,UAAI,QAA8C;AAElD,YAAM,mBAAmB,MAAY;AACnC,gBAAQ;AACR,mBAAW,OAAO,IAAI,YAAY,cAAc,OAAO,GAAG;AACxD,gBAAM,eAAe,IAAI,GAAG,QAAQ,OAAO,GAAG;AAC9C,cAAI,aAAa,WAAW,cAAc,GAAG;AAC3C,gBAAI,YAAY,iBAAiB,GAAG;AAAA,UACtC;AAAA,QACF;AACA,YAAI,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,MACrC;AAEA,YAAM,uBAAuB,CAAC,aAA2B;AACvD,YAAI,CAAC,cAAc,QAAQ,EAAG;AAC9B,YAAI,UAAU,KAAM,cAAa,KAAK;AACtC,gBAAQ,WAAW,kBAAkB,UAAU;AAAA,MACjD;AAEA,UAAI,QAAQ,GAAG,OAAO,oBAAoB;AAC1C,UAAI,QAAQ,GAAG,UAAU,oBAAoB;AAC7C,UAAI,QAAQ,GAAG,UAAU,oBAAoB;AAAA,IAC/C;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vite-plugin/services-typed-client.ts"],"sourcesContent":["/**\n * Vite plugin (T3.1) — generates a typed TS client from each service's\n * OpenAPI URL using `@hey-api/openapi-ts` (soft dep — graceful skip when\n * not installed; warns on failure).\n *\n * Dev-only (`apply: 'serve'`). Fire-and-forget — never blocks dev startup.\n *\n * D3: best-effort, warn-only. Generator failure must NEVER crash dev.\n */\nimport { resolve } from 'node:path'\n\nimport type { Plugin } from 'vite'\n\nimport { generateTypedClient, type ServicesConfig } from '../services/index.js'\n\ninterface ServicesTypedClientPluginOptions {\n cwd: string\n services: ServicesConfig\n}\n\nexport function servicesTypedClientPlugin(opts: ServicesTypedClientPluginOptions): Plugin {\n return {\n name: 'theokit:services-typed-client',\n apply: 'serve', // dev-only; never runs in build\n configureServer() {\n // EC-5: ordering — services-typed-client fires AFTER orchestrateDev's\n // healthcheck-gated readiness (orchestrateDev is awaited synchronously\n // in cli/commands/dev.ts BEFORE createServer, so by the time Vite's\n // configureServer hook fires, services are healthy).\n const outputDir = resolve(opts.cwd, 'clients')\n for (const [name, def] of Object.entries(opts.services)) {\n if (!def.openapi) continue\n const openapi = def.openapi\n void generateTypedClient({\n service: {\n name,\n runtime: def.runtime,\n port: def.port,\n proxy: def.proxy,\n dev: def.dev,\n start: def.start,\n healthcheck: def.healthcheck,\n cors: def.cors,\n passSetCookie: def.passSetCookie,\n openapi,\n ...(def.build !== undefined ? { build: def.build } : {}),\n ...(def.env !== undefined ? { env: def.env } : {}),\n ...(def.dependsOn !== undefined ? { dependsOn: def.dependsOn } : {}),\n },\n outputDir,\n log: (_level, msg) => {\n // Use console.warn unconditionally — eslint disallows console.log\n // in source code; warn is appropriate for \"best-effort with skip\"\n // semantics of this plugin.\n console.warn(`[services-typed-client] ${msg}`)\n },\n })\n }\n },\n }\n}\n"],"mappings":";;;;;;AASA,SAAS,eAAe;AAWjB,SAAS,0BAA0B,MAAgD;AACxF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,IACP,kBAAkB;AAKhB,YAAM,YAAY,QAAQ,KAAK,KAAK,SAAS;AAC7C,iBAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,KAAK,QAAQ,GAAG;AACvD,YAAI,CAAC,IAAI,QAAS;AAClB,cAAM,UAAU,IAAI;AACpB,aAAK,oBAAoB;AAAA,UACvB,SAAS;AAAA,YACP;AAAA,YACA,SAAS,IAAI;AAAA,YACb,MAAM,IAAI;AAAA,YACV,OAAO,IAAI;AAAA,YACX,KAAK,IAAI;AAAA,YACT,OAAO,IAAI;AAAA,YACX,aAAa,IAAI;AAAA,YACjB,MAAM,IAAI;AAAA,YACV,eAAe,IAAI;AAAA,YACnB;AAAA,YACA,GAAI,IAAI,UAAU,SAAY,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,YACtD,GAAI,IAAI,QAAQ,SAAY,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,YAChD,GAAI,IAAI,cAAc,SAAY,EAAE,WAAW,IAAI,UAAU,IAAI,CAAC;AAAA,UACpE;AAAA,UACA;AAAA,UACA,KAAK,CAAC,QAAQ,QAAQ;AAIpB,oBAAQ,KAAK,2BAA2B,GAAG,EAAE;AAAA,UAC/C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;","names":[]}