theokit 0.43.7 → 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 (396) hide show
  1. package/dist/{actions-virtual-module-LD3YKWIX.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-N7GKUS3D.js → actions-virtual-module-IK2SDIZ3.js} +9 -8
  4. package/dist/actions-virtual-module-IK2SDIZ3.js.map +1 -0
  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/{add-W6FNTAFS.js → add-3VLFUPF4.js} +1 -1
  10. package/dist/add-3VLFUPF4.js.map +1 -0
  11. package/dist/{agent-U35G3WNE.js → agent-OCCL5RUY.js} +5 -5
  12. package/dist/{agents-typed-client-CHMVW7MK.js → agents-typed-client-TNHCUU7Q.js} +2 -2
  13. package/dist/agents-typed-client-TNHCUU7Q.js.map +1 -0
  14. package/dist/{agents-typed-client-NJ73Z6XY.js → agents-typed-client-X56CQ6FX.js} +1 -1
  15. package/dist/agents-typed-client-X56CQ6FX.js.map +1 -0
  16. package/dist/{app-typed-client-6YZRDAPY.js → app-typed-client-4OH3JREB.js} +51 -12
  17. package/dist/app-typed-client-4OH3JREB.js.map +1 -0
  18. package/dist/{app-typed-client-V77SVU2O.js → app-typed-client-N5WUE2CP.js} +62 -24
  19. package/dist/app-typed-client-N5WUE2CP.js.map +1 -0
  20. package/dist/{aws-lambda-7GSCNLPY.js → aws-lambda-473QCOL6.js} +3 -3
  21. package/dist/{body-parser-web-MUWBKZ3F.js → body-parser-web-4Z2QTPDU.js} +2 -2
  22. package/dist/body-parser-web-4Z2QTPDU.js.map +1 -0
  23. package/dist/{body-parser-web-FV5HWCY3.js → body-parser-web-TIOTXPDW.js} +1 -1
  24. package/dist/body-parser-web-TIOTXPDW.js.map +1 -0
  25. package/dist/boot/index.js +4 -4
  26. package/dist/boot/index.js.map +1 -1
  27. package/dist/{broadcast-4SX7R5XE.js → broadcast-H64DJRBE.js} +3 -3
  28. package/dist/broadcast-H64DJRBE.js.map +1 -0
  29. package/dist/{build-BYLQZJT2.js → build-25RSFP22.js} +20 -11
  30. package/dist/build-25RSFP22.js.map +1 -0
  31. package/dist/{build-request-body-preview-EOP2Y4F3.js → build-request-body-preview-CKWWBF2M.js} +2 -2
  32. package/dist/build-request-body-preview-CKWWBF2M.js.map +1 -0
  33. package/dist/{build-request-body-preview-QMWD2IXK.js → build-request-body-preview-XPZ2GYMG.js} +1 -1
  34. package/dist/build-request-body-preview-XPZ2GYMG.js.map +1 -0
  35. package/dist/{bun-KP2KES6S.js → bun-OQUCL3XW.js} +3 -3
  36. package/dist/{check-PD2FTKMM.js → check-45NKUVSQ.js} +1 -1
  37. package/dist/check-45NKUVSQ.js.map +1 -0
  38. package/dist/{chunk-FRUEDJFX.js → chunk-226KMIGZ.js} +3 -3
  39. package/dist/{chunk-FRUEDJFX.js.map → chunk-226KMIGZ.js.map} +1 -1
  40. package/dist/{chunk-U3NTEK46.js → chunk-2ADKT37V.js} +5 -5
  41. package/dist/chunk-2ADKT37V.js.map +1 -0
  42. package/dist/{chunk-QWBXUZAX.js → chunk-37ZO643S.js} +9 -9
  43. package/dist/chunk-37ZO643S.js.map +1 -0
  44. package/dist/{chunk-ESC54TAK.js → chunk-3DGIANAN.js} +1 -1
  45. package/dist/chunk-3DGIANAN.js.map +1 -0
  46. package/dist/{chunk-GY5Q27BJ.js → chunk-47FFT43U.js} +1 -1
  47. package/dist/chunk-47FFT43U.js.map +1 -0
  48. package/dist/{chunk-JAIKGP3Q.js → chunk-47VNWTL4.js} +1 -1
  49. package/dist/chunk-47VNWTL4.js.map +1 -0
  50. package/dist/{chunk-ZJQ4O76G.js → chunk-4FWBKX6G.js} +1 -1
  51. package/dist/chunk-4FWBKX6G.js.map +1 -0
  52. package/dist/{chunk-NM4WF3XD.js → chunk-4PBJ266F.js} +1 -1
  53. package/dist/chunk-4PBJ266F.js.map +1 -0
  54. package/dist/{chunk-7MQOHNHE.js → chunk-52PDWL5S.js} +1 -1
  55. package/dist/chunk-52PDWL5S.js.map +1 -0
  56. package/dist/{chunk-JQJN6LFK.js → chunk-62EU2BVZ.js} +2 -2
  57. package/dist/chunk-62EU2BVZ.js.map +1 -0
  58. package/dist/{chunk-SJIRP73B.js → chunk-646CA6RV.js} +2 -2
  59. package/dist/chunk-646CA6RV.js.map +1 -0
  60. package/dist/{chunk-ASDXVRJD.js → chunk-6KDXP472.js} +4 -4
  61. package/dist/chunk-6KDXP472.js.map +1 -0
  62. package/dist/chunk-6LNIGF5P.js +46 -0
  63. package/dist/chunk-6LNIGF5P.js.map +1 -0
  64. package/dist/chunk-7IHWXDJJ.js +1 -0
  65. package/dist/chunk-7IHWXDJJ.js.map +1 -0
  66. package/dist/{chunk-JQSKBMXP.js → chunk-7LVM22VG.js} +1 -1
  67. package/dist/chunk-7LVM22VG.js.map +1 -0
  68. package/dist/{chunk-UVXB2ER7.js → chunk-7SABX4DJ.js} +1 -1
  69. package/dist/chunk-7SABX4DJ.js.map +1 -0
  70. package/dist/{chunk-P37RZRFV.js → chunk-A7JBF5FO.js} +1 -1
  71. package/dist/chunk-A7JBF5FO.js.map +1 -0
  72. package/dist/{chunk-X32XEJXR.js → chunk-ACKBVBUI.js} +1 -1
  73. package/dist/chunk-ACKBVBUI.js.map +1 -0
  74. package/dist/{chunk-UUFYP2UM.js → chunk-AM3QABW5.js} +3 -3
  75. package/dist/chunk-AM3QABW5.js.map +1 -0
  76. package/dist/{chunk-FH3DYBAN.js → chunk-ANS4ELRH.js} +1 -1
  77. package/dist/chunk-ANS4ELRH.js.map +1 -0
  78. package/dist/{chunk-UD3LFGDL.js → chunk-AVQU5CQF.js} +1 -1
  79. package/dist/chunk-AVQU5CQF.js.map +1 -0
  80. package/dist/{chunk-AFVTEAES.js → chunk-B7ONQ4QJ.js} +2 -2
  81. package/dist/{chunk-WSJKACWB.js → chunk-C34PMLSN.js} +1 -1
  82. package/dist/chunk-C34PMLSN.js.map +1 -0
  83. package/dist/{chunk-FEDSPOY6.js → chunk-DSOBIXXR.js} +6 -6
  84. package/dist/chunk-DSOBIXXR.js.map +1 -0
  85. package/dist/{chunk-AXVFVMLM.js → chunk-EIRQM55C.js} +12 -7
  86. package/dist/chunk-EIRQM55C.js.map +1 -0
  87. package/dist/{chunk-ZEGYW52B.js → chunk-FB2HBNIC.js} +1 -1
  88. package/dist/chunk-FB2HBNIC.js.map +1 -0
  89. package/dist/chunk-FXZZS7YW.js +605 -0
  90. package/dist/chunk-FXZZS7YW.js.map +1 -0
  91. package/dist/{chunk-5AVPWHUE.js → chunk-FZG3JYB3.js} +14 -50
  92. package/dist/chunk-FZG3JYB3.js.map +1 -0
  93. package/dist/{chunk-V3LJN2GS.js → chunk-GAPBPRPP.js} +1 -1
  94. package/dist/chunk-GAPBPRPP.js.map +1 -0
  95. package/dist/chunk-GUBN5UTT.js +1 -0
  96. package/dist/chunk-GUBN5UTT.js.map +1 -0
  97. package/dist/{chunk-GSO77FSM.js → chunk-HEK4SET7.js} +45 -47
  98. package/dist/chunk-HEK4SET7.js.map +1 -0
  99. package/dist/{chunk-SPJNVWOK.js → chunk-HRCNHWLP.js} +7 -110
  100. package/dist/chunk-HRCNHWLP.js.map +1 -0
  101. package/dist/{chunk-C3ZZ56YZ.js → chunk-JA2ZAT5Q.js} +1 -1
  102. package/dist/chunk-JA2ZAT5Q.js.map +1 -0
  103. package/dist/{chunk-KI7OWQUX.js → chunk-KBGGQK4I.js} +87 -27
  104. package/dist/chunk-KBGGQK4I.js.map +1 -0
  105. package/dist/{chunk-WFNLNIJX.js → chunk-KD5G3YXI.js} +4 -4
  106. package/dist/chunk-KD5G3YXI.js.map +1 -0
  107. package/dist/{chunk-VMEWD57H.js → chunk-KPKLIUUL.js} +1 -1
  108. package/dist/chunk-KPKLIUUL.js.map +1 -0
  109. package/dist/{chunk-IHJZPQEF.js → chunk-L4Y6RJ4E.js} +4 -4
  110. package/dist/chunk-L4Y6RJ4E.js.map +1 -0
  111. package/dist/{chunk-GUUWDMR2.js → chunk-L5Z6IY5U.js} +5 -5
  112. package/dist/chunk-L5Z6IY5U.js.map +1 -0
  113. package/dist/{chunk-L4GCEMNW.js → chunk-LNVMHQ2Z.js} +2 -2
  114. package/dist/{chunk-JBCHWRKF.js → chunk-MTEWLGVY.js} +2 -2
  115. package/dist/chunk-MTEWLGVY.js.map +1 -0
  116. package/dist/{chunk-5PU65T5W.js → chunk-MWP4YXWJ.js} +1 -1
  117. package/dist/chunk-MWP4YXWJ.js.map +1 -0
  118. package/dist/{chunk-IPC5UREF.js → chunk-NGEBYCOZ.js} +70 -46
  119. package/dist/chunk-NGEBYCOZ.js.map +1 -0
  120. package/dist/{chunk-5QW7IQQU.js → chunk-NTDN54XO.js} +1 -1
  121. package/dist/chunk-NTDN54XO.js.map +1 -0
  122. package/dist/{chunk-7BIM27LV.js → chunk-OIGJOYX6.js} +1 -1
  123. package/dist/chunk-OIGJOYX6.js.map +1 -0
  124. package/dist/{chunk-TSLSIRBR.js → chunk-OLGPYOLO.js} +2 -2
  125. package/dist/chunk-OLGPYOLO.js.map +1 -0
  126. package/dist/{chunk-NQRNDPZJ.js → chunk-OXCALO3W.js} +3 -3
  127. package/dist/{chunk-UNLG36Z5.js → chunk-Q5OT7LZG.js} +19 -499
  128. package/dist/chunk-Q5OT7LZG.js.map +1 -0
  129. package/dist/{chunk-45C3WUQ7.js → chunk-QPVXGC7O.js} +1 -1
  130. package/dist/chunk-QPVXGC7O.js.map +1 -0
  131. package/dist/{chunk-6NEXVBPY.js → chunk-RWNSW4FM.js} +1 -1
  132. package/dist/chunk-RWNSW4FM.js.map +1 -0
  133. package/dist/{chunk-E25FBYJP.js → chunk-SAFGDIPQ.js} +1 -1
  134. package/dist/chunk-SAFGDIPQ.js.map +1 -0
  135. package/dist/{chunk-RMU7EBRO.js → chunk-SEELKQUM.js} +2 -2
  136. package/dist/chunk-SEELKQUM.js.map +1 -0
  137. package/dist/{chunk-FOZIR3TG.js → chunk-SMP2T335.js} +2 -2
  138. package/dist/{chunk-D7ZH7DTG.js → chunk-SSYDEKWF.js} +1 -1
  139. package/dist/chunk-SSYDEKWF.js.map +1 -0
  140. package/dist/{chunk-2ZX4J4SX.js → chunk-SZU6BOMV.js} +114 -90
  141. package/dist/chunk-SZU6BOMV.js.map +1 -0
  142. package/dist/{chunk-M6NMS5LR.js → chunk-TDNNBLGW.js} +1 -1
  143. package/dist/chunk-TDNNBLGW.js.map +1 -0
  144. package/dist/{chunk-X2VVCJ4V.js → chunk-U267AOWS.js} +1 -1
  145. package/dist/chunk-U267AOWS.js.map +1 -0
  146. package/dist/{chunk-GW5H4KB4.js → chunk-U3H53IIN.js} +6 -6
  147. package/dist/chunk-U3H53IIN.js.map +1 -0
  148. package/dist/{chunk-OPLICDUB.js → chunk-UW2TEAGR.js} +1 -1
  149. package/dist/chunk-UW2TEAGR.js.map +1 -0
  150. package/dist/{chunk-IAJ2JVEH.js → chunk-VSLTKX5L.js} +1 -1
  151. package/dist/chunk-VSLTKX5L.js.map +1 -0
  152. package/dist/{chunk-YB5DJJ4U.js → chunk-VZTYCAO6.js} +1 -1
  153. package/dist/chunk-VZTYCAO6.js.map +1 -0
  154. package/dist/chunk-WDGYCBNI.js +65 -0
  155. package/dist/chunk-WDGYCBNI.js.map +1 -0
  156. package/dist/chunk-WPJOTQA5.js +1 -0
  157. package/dist/chunk-WPJOTQA5.js.map +1 -0
  158. package/dist/{chunk-DGUM43GV.js → chunk-XTHN6JBX.js} +1 -1
  159. package/dist/chunk-XTHN6JBX.js.map +1 -0
  160. package/dist/{chunk-HZ6USUHC.js → chunk-YOIVCCZT.js} +1 -1
  161. package/dist/chunk-YOIVCCZT.js.map +1 -0
  162. package/dist/{chunk-UO7WS32S.js → chunk-ZC2UESE4.js} +7 -5
  163. package/dist/chunk-ZC2UESE4.js.map +1 -0
  164. package/dist/cli/index.js +15 -15
  165. package/dist/client/core.d.ts +4 -258
  166. package/dist/client/core.js +13 -9
  167. package/dist/client/core.js.map +1 -1
  168. package/dist/client/index.d.ts +8 -97
  169. package/dist/client/index.js +39 -92
  170. package/dist/client/index.js.map +1 -1
  171. package/dist/{cloudflare-C6E5SPAE.js → cloudflare-4YT5CK3P.js} +3 -3
  172. package/dist/{controller-swc-transform-XV3ETSE3.js → controller-swc-transform-LUJ4VAZJ.js} +1 -1
  173. package/dist/controller-swc-transform-LUJ4VAZJ.js.map +1 -0
  174. package/dist/{controller-swc-transform-URELVCBM.js → controller-swc-transform-T7ZTD55V.js} +2 -2
  175. package/dist/controller-swc-transform-T7ZTD55V.js.map +1 -0
  176. package/dist/{cookies-MJKMGJ7N.js → cookies-SOXUQJXG.js} +3 -3
  177. package/dist/cookies-SOXUQJXG.js.map +1 -0
  178. package/dist/{db-3UNAMSFK.js → db-IVVCQE5Z.js} +1 -2
  179. package/dist/db-IVVCQE5Z.js.map +1 -0
  180. package/dist/{deno-deploy-RFZN56X4.js → deno-deploy-YYN6BNLF.js} +3 -3
  181. package/dist/{dev-PS2NGT6I.js → dev-V6UBM2OL.js} +14 -13
  182. package/dist/{dev-PS2NGT6I.js.map → dev-V6UBM2OL.js.map} +1 -1
  183. package/dist/{dev-emit-2DL3MCLM.js → dev-emit-F54Y5TIU.js} +5 -5
  184. package/dist/dev-emit-F54Y5TIU.js.map +1 -0
  185. package/dist/{dev-emit-XVTJXZGA.js → dev-emit-J7V6GWSK.js} +6 -6
  186. package/dist/devtools/entry.js +61 -25
  187. package/dist/devtools/entry.js.map +1 -1
  188. package/dist/{dispatcher-EJHL6JMJ.js → dispatcher-KMZBGMM5.js} +1 -1
  189. package/dist/dispatcher-KMZBGMM5.js.map +1 -0
  190. package/dist/dispatcher-MXKF3CRH.js +8 -0
  191. package/dist/dispatcher-MXKF3CRH.js.map +1 -0
  192. package/dist/{generate-5X4LG77M.js → generate-GXWPUVS7.js} +4 -5
  193. package/dist/generate-GXWPUVS7.js.map +1 -0
  194. package/dist/index.js +25 -25
  195. package/dist/index.js.map +1 -1
  196. package/dist/{info-2OOXFTJX.js → info-ICDTPJDC.js} +4 -4
  197. package/dist/info-ICDTPJDC.js.map +1 -0
  198. package/dist/{internal-api-FBFV265J.js → internal-api-ALXVBL56.js} +14 -14
  199. package/dist/{internal-api-LFW7R65K.js → internal-api-S7WK2WCY.js} +20 -22
  200. package/dist/internal-api-S7WK2WCY.js.map +1 -0
  201. package/dist/{load-config-3EAQTXPH.js → load-config-ICCNMO74.js} +4 -4
  202. package/dist/{mcp-ML4QK5ON.js → mcp-4JFODA5U.js} +6 -6
  203. package/dist/mcp-4JFODA5U.js.map +1 -0
  204. package/dist/{netlify-PMLHVPN4.js → netlify-2WZUQYI3.js} +3 -3
  205. package/dist/node-CMAYKDCM.js +10 -0
  206. package/dist/{openapi-2HVGJMSN.js → openapi-NIYA2VBG.js} +9 -9
  207. package/dist/openapi-NIYA2VBG.js.map +1 -0
  208. package/dist/react-query/index.js +2 -2
  209. package/dist/react-query/index.js.map +1 -1
  210. package/dist/registry-72YH7ZJM.js +23 -0
  211. package/dist/registry-72YH7ZJM.js.map +1 -0
  212. package/dist/{router-TLEAOFID.js → router-6MAACS25.js} +1 -2
  213. package/dist/router-6MAACS25.js.map +1 -0
  214. package/dist/{routes-XNS3UM3B.js → routes-2RD4S5PZ.js} +6 -6
  215. package/dist/schema-S553QCAU.js +22 -0
  216. package/dist/server/agent/index.d.ts +3 -72
  217. package/dist/server/agent/index.js +4 -4
  218. package/dist/server/agent/index.js.map +1 -1
  219. package/dist/server/auth/index.js +4 -4
  220. package/dist/server/auth/index.js.map +1 -1
  221. package/dist/server/cost/index.js +2 -2
  222. package/dist/server/cost/index.js.map +1 -1
  223. package/dist/server/cron/index.js +6 -6
  224. package/dist/server/cron/index.js.map +1 -1
  225. package/dist/server/define/index.js +4 -4
  226. package/dist/server/define/index.js.map +1 -1
  227. package/dist/server/http/index.js +11 -11
  228. package/dist/server/http/index.js.map +1 -1
  229. package/dist/server/index.d.ts +2 -2
  230. package/dist/server/index.js +41 -41
  231. package/dist/server/index.js.map +1 -1
  232. package/dist/server/jobs/index.js +7 -7
  233. package/dist/server/jobs/index.js.map +1 -1
  234. package/dist/server/observability/index.d.ts +9 -39
  235. package/dist/server/observability/index.js +5 -5
  236. package/dist/server/observability/index.js.map +1 -1
  237. package/dist/server/plugins/index.js +3 -3
  238. package/dist/server/plugins/index.js.map +1 -1
  239. package/dist/server/rate-limit/index.js +4 -4
  240. package/dist/server/rate-limit/index.js.map +1 -1
  241. package/dist/server/realtime/index.js +2 -2
  242. package/dist/server/realtime/index.js.map +1 -1
  243. package/dist/server/scan/index.js +7 -7
  244. package/dist/server/scan/index.js.map +1 -1
  245. package/dist/server/security/index.js +5 -5
  246. package/dist/server/security/index.js.map +1 -1
  247. package/dist/server/storage/index.js +2 -2
  248. package/dist/server/storage/index.js.map +1 -1
  249. package/dist/server/webhook/index.js +3 -3
  250. package/dist/server/webhook/index.js.map +1 -1
  251. package/dist/server-error-to-envelope-UM7DEVM4.js +8 -0
  252. package/dist/server-error-to-envelope-UM7DEVM4.js.map +1 -0
  253. package/dist/{server-routes-hmr-VZXOP5YT.js → server-routes-hmr-4W5L2MKN.js} +1 -1
  254. package/dist/server-routes-hmr-4W5L2MKN.js.map +1 -0
  255. package/dist/{server-routes-hmr-PBHSKCQJ.js → server-routes-hmr-6CRNSEWF.js} +2 -2
  256. package/dist/server-routes-hmr-6CRNSEWF.js.map +1 -0
  257. package/dist/{services-json-Y3XODNB5.js → services-json-7ZXJ4CIS.js} +1 -1
  258. package/dist/services-json-7ZXJ4CIS.js.map +1 -0
  259. package/dist/{services-typed-client-32KMTTXR.js → services-typed-client-EO4RFNTZ.js} +2 -2
  260. package/dist/services-typed-client-EO4RFNTZ.js.map +1 -0
  261. package/dist/{services-typed-client-IETY2SAK.js → services-typed-client-IXNOPVZA.js} +3 -3
  262. package/dist/services-typed-client-IXNOPVZA.js.map +1 -0
  263. package/dist/{start-YLR2SFHU.js → start-2LNPPG74.js} +40 -16
  264. package/dist/start-2LNPPG74.js.map +1 -0
  265. package/dist/{static-MB2CD5Y3.js → static-25H5K42R.js} +3 -3
  266. package/dist/{static-MB2CD5Y3.js.map → static-25H5K42R.js.map} +1 -1
  267. package/dist/{theo-cloud-DQRP6S6M.js → theo-cloud-JL2KXHIS.js} +2 -2
  268. package/dist/{upgrade-readiness-ACGW44JC.js → upgrade-readiness-77P6VNB4.js} +1 -1
  269. package/dist/upgrade-readiness-77P6VNB4.js.map +1 -0
  270. package/dist/{vercel-J6G7ZHYQ.js → vercel-FP3C6LN6.js} +3 -3
  271. package/dist/vite-plugin/index.js +25 -25
  272. package/dist/vite-plugin/index.js.map +1 -1
  273. package/dist/{vite-plugin-6VVU6PPC.js → vite-plugin-X6YO3OUH.js} +14 -13
  274. package/package.json +9 -8
  275. package/dist/actions-virtual-module-LD3YKWIX.js.map +0 -1
  276. package/dist/actions-virtual-module-N7GKUS3D.js.map +0 -1
  277. package/dist/add-W6FNTAFS.js.map +0 -1
  278. package/dist/agents-typed-client-CHMVW7MK.js.map +0 -1
  279. package/dist/agents-typed-client-NJ73Z6XY.js.map +0 -1
  280. package/dist/app-typed-client-6YZRDAPY.js.map +0 -1
  281. package/dist/app-typed-client-V77SVU2O.js.map +0 -1
  282. package/dist/body-parser-web-FV5HWCY3.js.map +0 -1
  283. package/dist/body-parser-web-MUWBKZ3F.js.map +0 -1
  284. package/dist/build-BYLQZJT2.js.map +0 -1
  285. package/dist/build-request-body-preview-EOP2Y4F3.js.map +0 -1
  286. package/dist/build-request-body-preview-QMWD2IXK.js.map +0 -1
  287. package/dist/check-PD2FTKMM.js.map +0 -1
  288. package/dist/chunk-2ZX4J4SX.js.map +0 -1
  289. package/dist/chunk-45C3WUQ7.js.map +0 -1
  290. package/dist/chunk-5AVPWHUE.js.map +0 -1
  291. package/dist/chunk-5PU65T5W.js.map +0 -1
  292. package/dist/chunk-5QW7IQQU.js.map +0 -1
  293. package/dist/chunk-6NEXVBPY.js.map +0 -1
  294. package/dist/chunk-7BIM27LV.js.map +0 -1
  295. package/dist/chunk-7MQOHNHE.js.map +0 -1
  296. package/dist/chunk-ASDXVRJD.js.map +0 -1
  297. package/dist/chunk-AXVFVMLM.js.map +0 -1
  298. package/dist/chunk-C3ZZ56YZ.js.map +0 -1
  299. package/dist/chunk-D7ZH7DTG.js.map +0 -1
  300. package/dist/chunk-E25FBYJP.js.map +0 -1
  301. package/dist/chunk-E3JH6YUM.js +0 -1
  302. package/dist/chunk-ESC54TAK.js.map +0 -1
  303. package/dist/chunk-FEDSPOY6.js.map +0 -1
  304. package/dist/chunk-FH3DYBAN.js.map +0 -1
  305. package/dist/chunk-GSO77FSM.js.map +0 -1
  306. package/dist/chunk-GUUWDMR2.js.map +0 -1
  307. package/dist/chunk-GW5H4KB4.js.map +0 -1
  308. package/dist/chunk-GY5Q27BJ.js.map +0 -1
  309. package/dist/chunk-HZ6USUHC.js.map +0 -1
  310. package/dist/chunk-IAJ2JVEH.js.map +0 -1
  311. package/dist/chunk-IHJZPQEF.js.map +0 -1
  312. package/dist/chunk-IPC5UREF.js.map +0 -1
  313. package/dist/chunk-JAIKGP3Q.js.map +0 -1
  314. package/dist/chunk-JBCHWRKF.js.map +0 -1
  315. package/dist/chunk-JHEAWR3L.js +0 -1
  316. package/dist/chunk-JQJN6LFK.js.map +0 -1
  317. package/dist/chunk-JQSKBMXP.js.map +0 -1
  318. package/dist/chunk-KI7OWQUX.js.map +0 -1
  319. package/dist/chunk-KPRJS3II.js +0 -505
  320. package/dist/chunk-KPRJS3II.js.map +0 -1
  321. package/dist/chunk-M6NMS5LR.js.map +0 -1
  322. package/dist/chunk-NM4WF3XD.js.map +0 -1
  323. package/dist/chunk-NPMCKOX4.js +0 -1
  324. package/dist/chunk-OPLICDUB.js.map +0 -1
  325. package/dist/chunk-P37RZRFV.js.map +0 -1
  326. package/dist/chunk-QWBXUZAX.js.map +0 -1
  327. package/dist/chunk-RMU7EBRO.js.map +0 -1
  328. package/dist/chunk-SJIRP73B.js.map +0 -1
  329. package/dist/chunk-SPJNVWOK.js.map +0 -1
  330. package/dist/chunk-TSLSIRBR.js.map +0 -1
  331. package/dist/chunk-U3NTEK46.js.map +0 -1
  332. package/dist/chunk-UD3LFGDL.js.map +0 -1
  333. package/dist/chunk-UNLG36Z5.js.map +0 -1
  334. package/dist/chunk-UO7WS32S.js.map +0 -1
  335. package/dist/chunk-UUFYP2UM.js.map +0 -1
  336. package/dist/chunk-UVXB2ER7.js.map +0 -1
  337. package/dist/chunk-V3LJN2GS.js.map +0 -1
  338. package/dist/chunk-VMEWD57H.js.map +0 -1
  339. package/dist/chunk-WFNLNIJX.js.map +0 -1
  340. package/dist/chunk-WSJKACWB.js.map +0 -1
  341. package/dist/chunk-X2VVCJ4V.js.map +0 -1
  342. package/dist/chunk-X32XEJXR.js.map +0 -1
  343. package/dist/chunk-YB5DJJ4U.js.map +0 -1
  344. package/dist/chunk-ZEGYW52B.js.map +0 -1
  345. package/dist/chunk-ZJQ4O76G.js.map +0 -1
  346. package/dist/controller-swc-transform-URELVCBM.js.map +0 -1
  347. package/dist/controller-swc-transform-XV3ETSE3.js.map +0 -1
  348. package/dist/cookies-MJKMGJ7N.js.map +0 -1
  349. package/dist/db-3UNAMSFK.js.map +0 -1
  350. package/dist/dev-emit-2DL3MCLM.js.map +0 -1
  351. package/dist/dispatcher-63LBXYLE.js +0 -8
  352. package/dist/dispatcher-63LBXYLE.js.map +0 -1
  353. package/dist/dispatcher-EJHL6JMJ.js.map +0 -1
  354. package/dist/generate-5X4LG77M.js.map +0 -1
  355. package/dist/info-2OOXFTJX.js.map +0 -1
  356. package/dist/internal-api-FBFV265J.js.map +0 -1
  357. package/dist/internal-api-LFW7R65K.js.map +0 -1
  358. package/dist/load-config-3EAQTXPH.js.map +0 -1
  359. package/dist/mcp-ML4QK5ON.js.map +0 -1
  360. package/dist/node-BPJ3Z4DT.js +0 -10
  361. package/dist/node-BPJ3Z4DT.js.map +0 -1
  362. package/dist/openapi-2HVGJMSN.js.map +0 -1
  363. package/dist/registry-FKPH7XEL.js +0 -24
  364. package/dist/registry-FKPH7XEL.js.map +0 -1
  365. package/dist/router-TLEAOFID.js.map +0 -1
  366. package/dist/schema-LYOLNHZ7.js +0 -30
  367. package/dist/schema-LYOLNHZ7.js.map +0 -1
  368. package/dist/server-error-to-envelope-EOFUIUNB.js +0 -8
  369. package/dist/server-error-to-envelope-EOFUIUNB.js.map +0 -1
  370. package/dist/server-routes-hmr-PBHSKCQJ.js.map +0 -1
  371. package/dist/server-routes-hmr-VZXOP5YT.js.map +0 -1
  372. package/dist/services-json-Y3XODNB5.js.map +0 -1
  373. package/dist/services-typed-client-32KMTTXR.js.map +0 -1
  374. package/dist/services-typed-client-IETY2SAK.js.map +0 -1
  375. package/dist/start-YLR2SFHU.js.map +0 -1
  376. package/dist/upgrade-readiness-ACGW44JC.js.map +0 -1
  377. package/dist/vite-plugin-6VVU6PPC.js.map +0 -1
  378. /package/dist/{agent-U35G3WNE.js.map → agent-OCCL5RUY.js.map} +0 -0
  379. /package/dist/{aws-lambda-7GSCNLPY.js.map → aws-lambda-473QCOL6.js.map} +0 -0
  380. /package/dist/{bun-KP2KES6S.js.map → bun-OQUCL3XW.js.map} +0 -0
  381. /package/dist/{chunk-AFVTEAES.js.map → chunk-B7ONQ4QJ.js.map} +0 -0
  382. /package/dist/{chunk-L4GCEMNW.js.map → chunk-LNVMHQ2Z.js.map} +0 -0
  383. /package/dist/{chunk-NQRNDPZJ.js.map → chunk-OXCALO3W.js.map} +0 -0
  384. /package/dist/{chunk-FOZIR3TG.js.map → chunk-SMP2T335.js.map} +0 -0
  385. /package/dist/{cloudflare-C6E5SPAE.js.map → cloudflare-4YT5CK3P.js.map} +0 -0
  386. /package/dist/{deno-deploy-RFZN56X4.js.map → deno-deploy-YYN6BNLF.js.map} +0 -0
  387. /package/dist/{dev-emit-XVTJXZGA.js.map → dev-emit-J7V6GWSK.js.map} +0 -0
  388. /package/dist/{broadcast-4SX7R5XE.js.map → internal-api-ALXVBL56.js.map} +0 -0
  389. /package/dist/{chunk-DGUM43GV.js.map → load-config-ICCNMO74.js.map} +0 -0
  390. /package/dist/{netlify-PMLHVPN4.js.map → netlify-2WZUQYI3.js.map} +0 -0
  391. /package/dist/{chunk-E3JH6YUM.js.map → node-CMAYKDCM.js.map} +0 -0
  392. /package/dist/{routes-XNS3UM3B.js.map → routes-2RD4S5PZ.js.map} +0 -0
  393. /package/dist/{chunk-JHEAWR3L.js.map → schema-S553QCAU.js.map} +0 -0
  394. /package/dist/{theo-cloud-DQRP6S6M.js.map → theo-cloud-JL2KXHIS.js.map} +0 -0
  395. /package/dist/{vercel-J6G7ZHYQ.js.map → vercel-FP3C6LN6.js.map} +0 -0
  396. /package/dist/{chunk-NPMCKOX4.js.map → vite-plugin-X6YO3OUH.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/storage/storage-manager.ts","../src/server/storage/use-unstorage.ts","../src/server/storage/use-database.ts"],"sourcesContent":["/**\n * StorageManager — per-process singleton coordinating pluggable storage\n * adapters (Postgres pools, Redis clients, in-memory adapters).\n *\n * Architectural decisions: see ADR-0007 (docs/adr/0007-storage-manager-singleton.md).\n *\n * Lifecycle:\n * 1. `getStorageManager()` returns the singleton (D1).\n * 2. `configure(storageConfig)` honored once per process (D3). Second call\n * warns and is ignored.\n * 3. `usePostgres(dbName, factory)` / `useRedis(serverName, factory)`\n * lazily create + cache (D2). Factory invoked exactly once per name.\n * 4. `register(adapter)` opts the adapter into the SIGTERM drain (D6).\n * 5. `dispose()` drains in parallel — adapters + pools + Redis (D5).\n * Idempotent. Adapter errors logged + swallowed (do not block shutdown).\n *\n * Test seam: `__resetForTests()` resets state. Test files MUST call it from\n * `beforeEach` to avoid pollution across `it` blocks (EC-3).\n */\n\nimport type {\n PoolLike,\n PostgresDatabaseConfig,\n RedisLike,\n RedisServerConfig,\n ServerConfig,\n StorageAdapter,\n StorageConfig,\n} from './storage-types.js'\n\nexport type PostgresFactory = (server: ServerConfig, db: PostgresDatabaseConfig) => PoolLike\nexport type RedisFactory = (config: RedisServerConfig) => RedisLike\n\nexport class StorageManager {\n #config: StorageConfig | undefined\n readonly #dbPools = new Map<string, PoolLike>()\n readonly #redisClients = new Map<string, RedisLike>()\n readonly #genericClients = new Map<string, unknown>()\n readonly #adapters = new Set<StorageAdapter>()\n #disposed = false\n\n /**\n * Apply the storage config block from `theo.config.ts`. Second call warns\n * and is ignored (D3). Reset only via `__resetForTests()`.\n */\n configure(config: StorageConfig): void {\n if (this.#config !== undefined) {\n console.warn('[theokit] StorageManager already configured; ignoring second configure() call')\n return\n }\n this.#config = config\n }\n\n /**\n * Generic cache + factory for ANY client (MySQL, Mongo, Turso, DynamoDB, …).\n * Returns the cached client OR invokes the factory and caches its return.\n *\n * EC-1 FIX: cache-hit check uses `Map.has(name)`, NOT `cached !== undefined`.\n * Factories returning `null` or `undefined` are valid (lazy connect, stubs);\n * the `!== undefined` check would re-invoke the factory infinitely for\n * undefined returns AND silently cache `null` cast as `T`.\n *\n * EC-2 (documented type hole): `useStorage<A>('x', fA)` followed by\n * `useStorage<B>('x', fB)` returns the cached A cast as B. Same trade-off\n * as `Map<string, unknown>` — caller is responsible for using unique names\n * per type.\n *\n * Lifecycle: generic clients are NOT auto-drained by `dispose()`. Call\n * `manager.register({ name, dispose })` separately to participate.\n *\n * Reserved name prefixes (do NOT use): `__pg:`, `__redis:`, `__unstorage:`,\n * `__db0:` — these are used internally by the typed helpers.\n */\n useStorage<T>(name: string, factory: () => T): T {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n if (this.#genericClients.has(name)) return this.#genericClients.get(name) as T\n const client = factory()\n this.#genericClients.set(name, client)\n return client\n }\n\n /**\n * Return a Postgres pool for the named database. Lazy + cached. Factory\n * invoked exactly once per `dbName` (D2).\n *\n * Throws if:\n * - manager is disposed\n * - `dbName` is not in `config.databases`\n * - the referenced server is not in `config.servers` (EC-2: deferred\n * cross-field validation surfaces here, not at configure time)\n */\n usePostgres(dbName: string, factory: PostgresFactory): PoolLike {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n const cached = this.#dbPools.get(dbName)\n if (cached !== undefined) return cached\n const dbConfig = this.#config?.databases?.[dbName]\n if (dbConfig === undefined) {\n throw new Error(\n `Database \"${dbName}\" not configured. Add it to theo.config.ts > storage.databases.`,\n )\n }\n const server = this.#config?.servers?.[dbConfig.server]\n if (server === undefined) {\n throw new Error(\n `Server \"${dbConfig.server}\" referenced by database \"${dbName}\" not found in theo.config.ts > storage.servers.`,\n )\n }\n const pool = factory(server, dbConfig)\n this.#dbPools.set(dbName, pool)\n return pool\n }\n\n /**\n * Return a Redis client for the named server. Lazy + cached. Factory\n * invoked exactly once per `serverName`.\n */\n useRedis(serverName: string, factory: RedisFactory): RedisLike {\n if (this.#disposed) throw new Error('StorageManager is disposed')\n const cached = this.#redisClients.get(serverName)\n if (cached !== undefined) return cached\n const serverConfig = this.#config?.redis?.[serverName]\n if (serverConfig === undefined) {\n throw new Error(\n `Redis server \"${serverName}\" not configured. Add it to theo.config.ts > storage.redis.`,\n )\n }\n const client = factory(serverConfig)\n this.#redisClients.set(serverName, client)\n return client\n }\n\n /**\n * Register an adapter for graceful shutdown coordination (D6).\n *\n * EC-4: throws if the manager is already disposed — prevents silent\n * leaks in test seams that reset+reuse the manager.\n */\n register(adapter: StorageAdapter): void {\n if (this.#disposed) {\n throw new Error(\n 'StorageManager is disposed; cannot register new adapter. Call __resetForTests() in test setup.',\n )\n }\n this.#adapters.add(adapter)\n }\n\n /**\n * Drain in parallel — registered adapters, then PG pools, then Redis\n * clients. Idempotent (D5). Errors per resource are logged and swallowed\n * — they do NOT prevent shutdown of the remaining resources.\n *\n * Caller must wrap in a timeout if a bound is needed (EC-7: documented).\n * In production, `start.ts` wraps the whole shutdown sequence in a 25 s\n * force-exit timer.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return\n this.#disposed = true\n\n const adapterDrains = Array.from(this.#adapters).map((a) =>\n a.dispose().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] adapter \"${a.name}\" dispose failed: ${msg}`)\n }),\n )\n await Promise.all(adapterDrains)\n\n const poolCloses = Array.from(this.#dbPools.values()).map((pool) => {\n // EC-5: pool without `.end()` is silently skipped (TS already enforces\n // shape at factory signature; this is the graceful runtime fallback if\n // user bypasses via `as` cast).\n if (pool.end === undefined) return Promise.resolve()\n return pool.end().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] PG pool close failed: ${msg}`)\n })\n })\n await Promise.all(poolCloses)\n\n const redisCloses = Array.from(this.#redisClients.values()).map((c) =>\n c.quit().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit] Redis quit failed, falling back to disconnect: ${msg}`)\n c.disconnect()\n }),\n )\n await Promise.all(redisCloses)\n\n this.#dbPools.clear()\n this.#redisClients.clear()\n this.#genericClients.clear()\n this.#adapters.clear()\n }\n\n /** @internal Testing seam — resets state so a single test file can re-use the singleton across `it` blocks (EC-3). */\n __resetForTests(): void {\n this.#config = undefined\n this.#dbPools.clear()\n this.#redisClients.clear()\n this.#genericClients.clear()\n this.#adapters.clear()\n this.#disposed = false\n }\n\n /** @internal Introspection helper for tests. */\n __isConfiguredForTests(): boolean {\n return this.#config !== undefined\n }\n\n /** @internal Introspection helper for tests. */\n __isDisposedForTests(): boolean {\n return this.#disposed\n }\n}\n\n// Singleton — one StorageManager per process (D1).\nlet __singleton: StorageManager | undefined\n\nexport function getStorageManager(): StorageManager {\n __singleton ??= new StorageManager()\n return __singleton\n}\n\n/** @internal Drops the module-level singleton — used by tests that need a fresh manager. */\nexport function __resetSingletonForTests(): void {\n __singleton = undefined\n}\n","/**\n * `useUnstorage(name, driver?)` — wraps `unstorage.createStorage({ driver })`\n * via `StorageManager.useStorage<T>` for caching + lifecycle coordination.\n *\n * Architectural decision: see ADR-0009 (`unstorage` adoption for KV drivers).\n *\n * `unstorage` is an OPTIONAL peer dependency. Apps that don't use KV pay zero\n * bundle cost; calling `useUnstorage` without `unstorage` installed throws an\n * actionable error.\n *\n * The returned `Storage<T>` is auto-registered with `manager.dispose()` so\n * SIGTERM drains it. Repeated calls with the same `name` return the cached\n * instance (factory invoked once).\n *\n * @example Memory driver (dev / tests)\n * const cache = await useUnstorage<string>('cache')\n * await cache.setItem('k', 'v')\n *\n * @example Redis driver (prod)\n * import redisDriver from 'unstorage/drivers/redis'\n * const cache = await useUnstorage<string>('cache', redisDriver({ url: process.env.REDIS_URL }))\n *\n * Reserved cache key prefix: `__unstorage:` — do NOT use this prefix in\n * `manager.useStorage<T>(name)` to avoid collision (EC-8 documented).\n */\nimport { getStorageManager } from './storage-manager.js'\n\ninterface UnstorageModule {\n createStorage: <T>(options?: { driver?: unknown }) => UnstorageInstance<T>\n}\n\nexport interface UnstorageInstance<T> {\n getItem(key: string): Promise<T | null>\n setItem(key: string, value: T): Promise<void>\n removeItem(key: string): Promise<void>\n keys(prefix?: string): Promise<string[]>\n hasItem(key: string): Promise<boolean>\n clear(prefix?: string): Promise<void>\n dispose?(): Promise<void>\n}\n\nexport async function useUnstorage<T = unknown>(\n name: string,\n driver?: unknown,\n): Promise<UnstorageInstance<T>> {\n const mod = (await import('unstorage').catch(() => null)) as UnstorageModule | null\n if (mod === null) {\n throw new Error(\n \"useUnstorage requires the 'unstorage' package. Install via: pnpm add unstorage\",\n )\n }\n const manager = getStorageManager()\n return manager.useStorage<UnstorageInstance<T>>(`__unstorage:${name}`, () => {\n const storage = mod.createStorage<T>({ driver })\n manager.register({\n name: `unstorage:${name}`,\n dispose: () => storage.dispose?.() ?? Promise.resolve(),\n })\n return storage\n })\n}\n","/**\n * `useDatabase(name, connector)` — wraps `db0.createDatabase(connector)` via\n * `StorageManager.useStorage<T>` for caching.\n *\n * Architectural decision: see ADR-0010 (db0 adoption for SQL non-Postgres).\n *\n * `db0` is an OPTIONAL peer dependency. Apps that don't use db0 pay zero bundle\n * cost; calling `useDatabase` without `db0` installed throws an actionable error.\n *\n * **For Postgres: prefer `usePostgres(dbName, factory)`** — returns `pg.Pool`\n * directly which integrates better with Drizzle / raw SQL.\n *\n * Lifecycle: db0 connectors don't share a unified close API. Users SHOULD\n * register a dispose hook manually if cleanup matters (EC-9 documented):\n * ```ts\n * const db = await useDatabase('main', sqlite({...}))\n * getStorageManager().register({ name: 'db:main', dispose: () => db.exec('...') })\n * ```\n *\n * Reserved cache key prefix: `__db0:` — do NOT use this prefix in\n * `manager.useStorage<T>(name)` to avoid collision (EC-8 documented).\n *\n * @example libSQL / Turso\n * import libsql from 'db0/connectors/libsql-core'\n * const db = await useDatabase('main', libsql({ url: process.env.TURSO_URL }))\n *\n * @example SQLite (better-sqlite3)\n * import sqlite from 'db0/connectors/better-sqlite3'\n * const db = await useDatabase('main', sqlite({ name: 'app.db' }))\n */\nimport { getStorageManager } from './storage-manager.js'\n\ninterface Db0Module {\n createDatabase: (connector: unknown) => Db0Database\n}\n\nexport interface Db0Database {\n exec: (sql: string) => Promise<unknown>\n prepare: (sql: string) => unknown\n sql: (strings: TemplateStringsArray, ...args: unknown[]) => Promise<unknown>\n}\n\n/**\n * EC-5 — heuristic to detect a connector factory that wasn't invoked.\n *\n * `db0` connectors are factories: `sqlite({...})` returns a `Connector`.\n * If the user passes `sqlite` (the factory itself) instead of `sqlite({...})`\n * (the invoked Connector), `db0.createDatabase` fails with a cryptic message.\n *\n * A connector is an object (with methods like `exec`, `prepare`). A factory\n * is a function with declared parameters (arity ≥ 1, since connectors take\n * an options object). We throw an actionable error in the factory case.\n */\nfunction detectUninvokedFactory(connector: unknown): string | null {\n if (typeof connector === 'function') {\n const fn = connector as { length?: number; name?: string }\n if ((fn.length ?? 0) > 0) {\n const name = fn.name !== undefined && fn.name !== '' ? fn.name : 'connector'\n return (\n `useDatabase: connector argument looks like an un-invoked factory (function with ${String(fn.length)} parameter(s)). ` +\n `Did you forget to call the factory? Pass \\`${name}({...})\\` not \\`${name}\\`. ` +\n `Example: useDatabase('main', sqlite({ name: ':memory:' }))`\n )\n }\n }\n return null\n}\n\nexport async function useDatabase(name: string, connector: unknown): Promise<Db0Database> {\n const factoryError = detectUninvokedFactory(connector)\n if (factoryError !== null) throw new Error(factoryError)\n\n const mod = (await import('db0').catch(() => null)) as Db0Module | null\n if (mod === null) {\n throw new Error(\"useDatabase requires the 'db0' package. Install via: pnpm add db0\")\n }\n const manager = getStorageManager()\n return manager.useStorage<Db0Database>(`__db0:${name}`, () => mod.createDatabase(connector))\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/agent/run-terminal-agent.ts","../src/server/agent/render-terminal.ts","../src/cli/commands/agent.ts"],"sourcesContent":["/**\n * M5 (theokit-ai-first) — run a compiled agent in the terminal, reusing the M4 harness.\n *\n * The web `mountAgent` renders `streamAgentUIMessages` to an HTTP `Response` and resolves HITL\n * approvals via the approve route. This is its terminal sibling: SAME `compileAgentModule` + SAME\n * `streamAgentUIMessages` + SAME in-process approval registry — only the render surface (stdout) and\n * the approval input (a readline prompt) differ (ADR 0039 D2). In a single-process CLI the stream's\n * `awaitApproval` (→ `registry.register`) and the terminal prompt (→ `registry.resolve`) share the one\n * in-process registry — the M4 singleton's exact fit. No new runtime, no LLM call, no tool dispatch.\n */\nimport {\n compileAgentModule,\n streamAgentUIMessages,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\n\nimport { getApprovalRegistry, type ApprovalRegistry } from './approval-registry.js'\nimport {\n promptTerminalApproval,\n renderAgentStreamToTerminal,\n type TerminalOut,\n} from './render-terminal.js'\n\nexport interface RunTerminalAgentInput {\n message: string\n /** Resume key; a fresh id per run when omitted. */\n sessionId?: string\n /** Injectable sink (defaults to `process.stdout` at the CLI entry). */\n stdout?: TerminalOut\n /** Injectable shared registry (defaults to the process singleton). */\n registry?: ApprovalRegistry\n /** Injectable approval prompt (defaults to the readline prompt over the process streams). */\n promptApproval?: (req: { toolName: string; timeoutMs?: number }) => Promise<boolean>\n /** Labels a fail-fast `AgentDefinitionError` (the file path). */\n source?: string\n}\n\n/**\n * Compile the module (M4, gathers `@Mixin` toolboxes), stream it, and render to the terminal. When the\n * agent has `@HumanInTheLoop`-gated tools, the terminal prompts and resolves the shared registry —\n * approve runs the tool, deny/timeout/non-TTY denies and the run continues.\n */\nexport async function runAgentInTerminal(\n mod: unknown,\n apiKey: string,\n input: RunTerminalAgentInput,\n): Promise<{ sawError: boolean }> {\n const compiled = compileAgentModule(mod, input.source)\n const stdout = input.stdout ?? process.stdout\n const registry = input.registry ?? getApprovalRegistry()\n const sessionId = input.sessionId ?? crypto.randomUUID()\n const promptApproval =\n input.promptApproval ??\n ((req: { toolName: string; timeoutMs?: number }) =>\n promptTerminalApproval(req, { input: process.stdin, output: process.stdout }, req.timeoutMs))\n\n // Mirror mount-agent's HITL wiring; absent ⇒ the non-HITL stream path (M2), unchanged.\n const gated = compiled.hitl\n const hitl =\n gated && gated.size > 0\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions) =>\n registry.register(approvalId, {\n timeoutMs: opts.timeout ?? 300_000,\n onTimeout: opts.onTimeout ?? 'abort',\n }),\n }\n : undefined\n\n const chunks = streamAgentUIMessages(compiled, apiKey, {\n message: input.message,\n sessionId,\n hitl,\n })\n return renderAgentStreamToTerminal(chunks, {\n stdout,\n onApproval: async ({ approvalId, toolName }) => {\n // The renderer paused here (the SDK run is paused in its awaited pre_tool_call hook); prompt,\n // then resolve the SAME registry the stream registered into — the run resumes with the decision.\n // The prompt shares the gated tool's timeout so it can never outlive the registry-settled run.\n const timeoutMs = gated?.get(toolName)?.timeout ?? 300_000\n const approved = await promptApproval({ toolName, timeoutMs })\n registry.resolve(approvalId, approved)\n },\n })\n}\n","/**\n * M5 (theokit-ai-first) — the terminal render surface for the M4 agent harness.\n *\n * Consumes the M4 `UIMessageChunk` stream (`streamAgentUIMessages`) and writes it to the terminal:\n * streaming text, a dim reasoning line, `▸ tool(input)` / result cards, a checkpoint notice, errors.\n * On a HITL `tool-approval-request` it delegates to the injected `onApproval` (the entry wires that to\n * a readline prompt + the approval registry). This is the ONLY new code M5 adds — no runtime, no LLM\n * call, no tool dispatch (ADR 0039 D2). I/O is INJECTED (`stdout`, prompt streams) so the whole\n * surface is deterministically testable without a TTY (ADR 0039 D4 / ADR-M5a).\n */\nimport { createInterface } from 'node:readline'\nimport type { Readable, Writable } from 'node:stream'\n\nimport type { UIMessageChunk } from 'ai'\n\n/** ANSI helpers — applied only on a TTY so a captured (non-TTY) sink stays plain-text for tests. */\nconst ANSI = {\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n green: '\\x1b[32m',\n reset: '\\x1b[0m',\n}\nfunction paint(text: string, color: keyof typeof ANSI, tty: boolean | undefined): string {\n return tty ? `${ANSI[color]}${text}${ANSI.reset}` : text\n}\n\n/** A stdout-like sink; `isTTY` gates color (Node's `process.stdout` carries it, a test sink does not). */\nexport type TerminalOut = Writable & { isTTY?: boolean }\n\nexport interface TerminalRenderOptions {\n stdout: TerminalOut\n /** Resolve a HITL approval (the entry prompts + resolves the registry). */\n onApproval: (req: { approvalId: string; toolName: string }) => Promise<void>\n}\n\n/** Render an `unknown` tool output/input for display (string as-is; other JSON-ish values stringified). */\nfunction display(value: unknown): string {\n return typeof value === 'string' ? value : JSON.stringify(value ?? {})\n}\n\n/**\n * Map a single (non-approval) `UIMessageChunk` to its terminal line(s), or `''` for chunks that render\n * nothing (`start`/`text-start`/`reasoning-start`/…). Pure — the async loop owns approval + I/O.\n * Narrows the `ai` discriminated union directly on `chunk.type` (no cast; G3).\n */\nfunction renderChunkLine(chunk: UIMessageChunk, tty: boolean | undefined): string {\n switch (chunk.type) {\n case 'text-delta':\n return chunk.delta\n case 'text-end':\n case 'finish':\n return '\\n'\n case 'reasoning-delta':\n return paint(chunk.delta, 'dim', tty)\n case 'reasoning-end':\n return '\\n'\n case 'tool-input-available':\n return paint(`\\n▸ ${chunk.toolName}(${display(chunk.input)})\\n`, 'cyan', tty)\n case 'tool-output-available':\n return paint(` ✓ ${display(chunk.output)}\\n`, 'green', tty)\n case 'tool-output-error':\n return paint(` ✗ ${chunk.errorText}\\n`, 'red', tty)\n case 'error':\n return paint(`\\n✗ ${chunk.errorText}\\n`, 'red', tty)\n default:\n // ai-sdk `data-*` transient parts (e.g. the M4 `data-checkpoint` resume handle).\n return chunk.type === 'data-checkpoint'\n ? paint('\\n⎇ checkpoint saved — resume with the same session id\\n', 'dim', tty)\n : ''\n }\n}\n\n/**\n * Render the `UIMessageChunk` stream to `stdout`. Sequential: one chunk → its line(s). The SDK run is\n * already paused inside the awaited `pre_tool_call` hook when a `tool-approval-request` arrives, so a\n * blocking `await onApproval(...)` here is correct — no frame buffer, no concurrency. Returns whether\n * an `error` chunk was seen so the caller can signal a non-zero exit (fail-loud, not silent exit 0).\n */\nexport async function renderAgentStreamToTerminal(\n chunks: AsyncIterable<UIMessageChunk>,\n opts: TerminalRenderOptions,\n): Promise<{ sawError: boolean }> {\n const tty = opts.stdout.isTTY\n const write = (s: string): void => {\n if (s) opts.stdout.write(s)\n }\n // toolCallId → toolName, so the approval prompt (whose chunk carries only ids) can name the tool.\n const toolNames = new Map<string, string>()\n let sawError = false\n\n for await (const chunk of chunks) {\n if (chunk.type === 'tool-approval-request') {\n const toolName = toolNames.get(chunk.toolCallId)\n await opts.onApproval({ approvalId: chunk.approvalId, toolName: toolName ?? 'tool' })\n continue\n }\n if (chunk.type === 'tool-input-available' && chunk.toolCallId) {\n toolNames.set(chunk.toolCallId, chunk.toolName)\n }\n if (chunk.type === 'error') sawError = true\n write(renderChunkLine(chunk, tty))\n }\n return { sawError }\n}\n\n/** Injectable I/O for the approval prompt (defaults to the process streams at the CLI entry). */\nexport interface PromptIO {\n input: Readable & { isTTY?: boolean }\n output: Writable & { isTTY?: boolean }\n}\n\n/**\n * Prompt `Approve <tool>? (y/N)` and resolve to the decision. A non-interactive environment (piped /\n * CI — either stream is not a TTY) AUTO-DENIES without prompting (ADR-M5b fail-safe: a decision that\n * cannot be obtained is a deny, never an auto-approve; mirrors HITL `onTimeout: 'abort'`).\n *\n * `timeoutMs` (the gated tool's `@HumanInTheLoop` timeout) auto-denies + closes the prompt when the\n * human walks away — WITHOUT it the readline question would outlive the SDK run (the registry settles\n * the run at its own timeout but readline has no timer), hanging the CLI on a now-irrelevant prompt.\n */\nexport function promptTerminalApproval(\n req: { toolName: string },\n io: PromptIO,\n timeoutMs?: number,\n): Promise<boolean> {\n if (!io.input.isTTY || !io.output.isTTY) {\n io.output.write(`\\n⚠ Non-interactive terminal — auto-denying approval for '${req.toolName}'.\\n`)\n return Promise.resolve(false)\n }\n return new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: io.input, output: io.output })\n let timer: ReturnType<typeof setTimeout> | undefined\n const settle = (approved: boolean): void => {\n if (timer) clearTimeout(timer)\n rl.close()\n resolve(approved)\n }\n if (timeoutMs !== undefined && timeoutMs > 0) {\n timer = setTimeout(() => {\n io.output.write(`\\n⚠ Approval timed out — denying '${req.toolName}'.\\n`)\n settle(false)\n }, timeoutMs)\n }\n rl.question(`\\n❔ Approve ${req.toolName}? (y/N) `, (answer) => {\n const a = answer.trim().toLowerCase()\n settle(a === 'y' || a === 'yes')\n })\n })\n}\n","/**\n * M5 (theokit-ai-first) — `theokit agent <name> [message]`: run a scanned agent in the terminal.\n *\n * Scans the top-level `agents/` directory (M2), loads the matched `agents/<name>.ts` through the\n * framework's OWN Vite pipeline (the SAME transpile `theokit dev` uses — legacy `@Agent`/`@Tool`\n * decorators need it; Node's native loader can't transpile TS), resolves the provider apiKey, and\n * runs it in the terminal via the M4 harness (`runAgentInTerminal`). A dev-time surface (ADR 0039 D1),\n * not a CLI product. `deps` is injectable so the fail-fast branches are tested without Vite/an LLM.\n */\nimport { loadEnv } from '../../config/load-env.js'\nimport { resolveProvider } from '../../server/agent/provider-resolver.js'\nimport { runAgentInTerminal } from '../../server/agent/run-terminal-agent.js'\nimport { scanAgents } from '../../server/scan/agent-scan.js'\n\nexport interface AgentCommandDeps {\n projectRoot?: string\n /** Load a TS agent module; defaults to a fresh minimal Vite SSR server (framework transpile). */\n loadModule?: (filePath: string) => Promise<Record<string, unknown>>\n runAgent?: typeof runAgentInTerminal\n resolveApiKey?: () => string\n /** Agents dir name (config `agentsDir`); defaults to the loaded config's value (\"agents\"). Tests inject it. */\n agentsDir?: string\n}\n\nexport async function agentCommand(\n name: string,\n message: string | undefined,\n deps: AgentCommandDeps = {},\n): Promise<{ sawError: boolean }> {\n if (!message || message.trim().length === 0) {\n throw new Error('theokit agent: a message is required. Usage: theokit agent <name> \"<message>\"')\n }\n const projectRoot = deps.projectRoot ?? process.cwd()\n // #95 follow-up — resolve the agents dir from config (default \"agents\") so `agentsDir: \"core/agents\"`\n // is honored in the terminal, same as dev. Tests inject `agentsDir` to skip the config load.\n const agentsDir =\n deps.agentsDir ??\n (await (await import('../../config/load-config.js')).loadConfig(projectRoot)).agentsDir\n const agents = scanAgents(projectRoot, agentsDir)\n const agent = agents.find((a) => a.name === name)\n if (!agent) {\n const names = agents.map((a) => a.name).join(', ') || `(none found in ${agentsDir}/)`\n throw new Error(`theokit agent: '${name}' not found. Available agents: ${names}`)\n }\n\n const runAgent = deps.runAgent ?? runAgentInTerminal\n // Load .env into process.env BEFORE resolving the provider key — parity with `theokit dev`\n // (dev.ts loadEnv), so an app with OPENROUTER_API_KEY in .env works here too (review H1). Skipped\n // when the key is injected (tests) — a temp project has no .env anyway.\n const apiKey =\n deps.resolveApiKey?.() ??\n (() => {\n loadEnv({ cwd: projectRoot, mode: 'development' })\n return resolveProvider().apiKey\n })()\n\n let loadModule = deps.loadModule\n let dispose: (() => Promise<void>) | undefined\n if (!loadModule) {\n const loader = await createAgentSsrLoader(projectRoot)\n loadModule = loader.load\n dispose = loader.dispose\n }\n\n try {\n const mod = await loadModule(agent.filePath)\n return await runAgent(mod, apiKey, { message, source: agent.filePath })\n } finally {\n if (dispose) await dispose()\n }\n}\n\n/**\n * A minimal middleware-mode Vite server built from the SAME `theoPluginAsync` plugin set `theokit dev`\n * uses (`cli/commands/dev.ts`) — so a class-decorated `agents/<name>.ts` transpiles identically. No\n * HTTP listen, no service orchestration, silent — just `ssrLoadModule`. Caller MUST `dispose()`.\n */\nexport async function createAgentSsrLoader(projectRoot: string): Promise<{\n load: (p: string) => Promise<Record<string, unknown>>\n dispose: () => Promise<void>\n}> {\n const { createServer } = await import('vite')\n const react = (await import('@vitejs/plugin-react')).default\n const { theoPluginAsync } = await import('../../vite-plugin/index.js')\n const { loadConfig } = await import('../../config/load-config.js')\n // Pass the project's ssr/services/optimizeDeps config through, exactly as dev.ts does — so an app\n // that customizes ssr.noExternal transpiles here identically to `theokit dev` (review L1).\n const config = await loadConfig(projectRoot)\n const theoPlugins = await theoPluginAsync({\n root: projectRoot,\n ssr: config.ssr,\n services: config.services,\n viteOptimizeDeps: config.viteOptimizeDeps,\n // #95 — honor config `serverDir` so the terminal SSR loader transpiles under the same root as dev.\n serverDir: config.serverDir,\n // #95 follow-up — honor config `agentsDir`.\n agentsDir: config.agentsDir,\n })\n const server = await createServer({\n root: projectRoot,\n plugins: [react(), ...theoPlugins],\n server: { middlewareMode: true },\n appType: 'custom',\n logLevel: 'silent',\n })\n return {\n load: (p) => server.ssrLoadModule(p) as Promise<Record<string, unknown>>,\n dispose: async () => {\n // Surface a cleanup failure instead of swallowing it (error-handling.md) — non-fatal, the run\n // already finished, so warn rather than throw past the CLI boundary.\n await server.close().catch((err: unknown) => {\n console.warn('[theokit agent] Vite server cleanup error:', err)\n })\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/client/react-query-adapter.ts"],"sourcesContent":["/**\n * T5.3 — React Query adapter primitives.\n *\n * Exposes:\n * - `stableQueryKey(path, options)` — produces a deterministic queryKey\n * that is equal across calls when the logical content of query/body is\n * equal, regardless of property order or inline-object identity. Solves\n * EC-10 (inline-object → infinite refetch).\n * - `buildUseTheoQueryConfig(path, options, fetcher)` — produces the\n * `{ queryKey, queryFn }` pair that consumers pass to `useQuery` from\n * `@tanstack/react-query`.\n *\n * The canonical implementation lives here in `theokit/client`. The\n * dedicated subpath `theokit/react-query` re-exports this surface so\n * consumers who only need the React Query bridge can import from a\n * clearly named entry point — same shape as `theokit/server`,\n * `theokit/vite-plugin`, etc. No separate npm package.\n */\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => stableStringify(v)).join(',') + ']'\n }\n const keys = Object.keys(value).sort((a, b) => a.localeCompare(b))\n const pairs = keys.map(\n (k) => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]),\n )\n return '{' + pairs.join(',') + '}'\n}\n\nexport interface FetchOptionsLike {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\nexport type QueryKey = readonly unknown[]\n\nexport function stableQueryKey(path: string, options: FetchOptionsLike): QueryKey {\n const key: unknown[] = [path]\n if (options.query !== undefined) {\n key.push({ kind: 'query', payload: stableStringify(options.query) })\n }\n if (options.body !== undefined) {\n key.push({ kind: 'body', payload: stableStringify(options.body) })\n }\n if (options.params !== undefined) {\n key.push({ kind: 'params', payload: stableStringify(options.params) })\n }\n return key\n}\n\nexport type Fetcher<TResult = unknown> = (\n path: string,\n options: FetchOptionsLike,\n) => Promise<TResult>\n\nexport interface UseTheoQueryConfig<TResult = unknown> {\n queryKey: QueryKey\n queryFn: () => Promise<TResult>\n}\n\nexport function buildUseTheoQueryConfig<TResult = unknown>(\n path: string,\n options: FetchOptionsLike,\n fetcher: Fetcher<TResult>,\n): UseTheoQueryConfig<TResult> {\n return {\n queryKey: stableQueryKey(path, options),\n queryFn: () => fetcher(path, options),\n }\n}\n"],"mappings":";AAmBA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI;AAAA,EAChE;AACA,QAAM,OAAO,OAAO,KAAK,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACjE,QAAM,QAAQ,KAAK;AAAA,IACjB,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAiB,MAAkC,CAAC,CAAC;AAAA,EACxF;AACA,SAAO,MAAM,MAAM,KAAK,GAAG,IAAI;AACjC;AAUO,SAAS,eAAe,MAAc,SAAqC;AAChF,QAAM,MAAiB,CAAC,IAAI;AAC5B,MAAI,QAAQ,UAAU,QAAW;AAC/B,QAAI,KAAK,EAAE,MAAM,SAAS,SAAS,gBAAgB,QAAQ,KAAK,EAAE,CAAC;AAAA,EACrE;AACA,MAAI,QAAQ,SAAS,QAAW;AAC9B,QAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,gBAAgB,QAAQ,IAAI,EAAE,CAAC;AAAA,EACnE;AACA,MAAI,QAAQ,WAAW,QAAW;AAChC,QAAI,KAAK,EAAE,MAAM,UAAU,SAAS,gBAAgB,QAAQ,MAAM,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAYO,SAAS,wBACd,MACA,SACA,SAC6B;AAC7B,SAAO;AAAA,IACL,UAAU,eAAe,MAAM,OAAO;AAAA,IACtC,SAAS,MAAM,QAAQ,MAAM,OAAO;AAAA,EACtC;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/schema.ts","../src/config/schemas/header-safe.ts","../src/config/schemas/rate-limit.ts","../src/config/schemas/upload.ts","../src/config/schemas/logging.ts","../src/config/schemas/cache.ts","../src/config/schemas/security.ts","../src/config/schemas/storage.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { servicesConfigSchema } from '../services/index.js'\n\nimport {\n cacheSchema,\n loggingSchema,\n rateLimitSchema,\n securitySchema,\n storageSchema,\n uploadSchema,\n type FormatErrorContext,\n type FormatErrorHook,\n} from './schemas/index.js'\n\n// Re-export per-concern schemas + types so downstream consumers\n// (`adapters/*`, vite-plugin, generators, tests) keep their existing\n// imports valid. Per plan T2.3 — pure structural split; zero behavior\n// change at the call site.\nexport {\n cacheSchema,\n corsSchema,\n disallowedConfigSchema,\n headerSafeString,\n loggingSchema,\n rateLimitSchema,\n securityHeadersSchema,\n securitySchema,\n storageSchema,\n uploadSchema,\n} from './schemas/index.js'\n\nexport type { FormatErrorContext, FormatErrorHook, StorageConfig } from './schemas/index.js'\n\n/**\n * Root `theo.config.ts` schema — composer assembled from the per-concern\n * primitives re-exported above.\n *\n * Embedded blocks that exist ONLY as part of the root config (`agents`,\n * `ui`, `devtools`, `jobs`, `openapi`) stay inline below. They have no\n * external consumers; splitting them would create lonely files (M5\n * smell) without comprehension benefit.\n */\nexport const theoConfigSchema = z\n .object({\n /**\n * Plan v1.2 T2.1 — project identifier propagated into\n * `.theokit/services.json` v2 `project` field. DNS-1123 compatible (drives\n * Gitea repo + ArgoCD App naming on TheoCloud). When omitted, the build\n * emits services.json v1 with the legacy `services-bundle` fallback\n * (ADR D10) and logs a deprecation warning.\n */\n name: z\n .string()\n .max(63)\n .refine(\n // DNS-1123 equivalent expressed as explicit single-char anchors so\n // security/detect-unsafe-regex stays clean (no backtracking).\n (value) =>\n value.length > 0 &&\n /^[a-z0-9-]+$/u.test(value) &&\n !value.startsWith('-') &&\n !value.endsWith('-'),\n 'name must match DNS-1123 (lowercase alphanumeric+hyphens, 1-63 chars, no leading/trailing hyphen)',\n )\n .optional(),\n appDir: z.string().default('app'),\n serverDir: z.string().default('server'),\n /**\n * Directory (relative to the project root) holding `agents/<name>.ts` definitions, scanned by\n * dev/build/terminal/mcp. Defaults to `\"agents\"` — apps that keep the canonical layout are\n * unaffected. Set to e.g. `\"core/agents\"` to organize agents under a domain root (#95 follow-up).\n */\n agentsDir: z.string().default('agents'),\n /**\n * T2.2 / EC-4 — Build output directory. Must be a relative path inside\n * the project root. Refused absolute or parent-relative paths to prevent\n * `cleanOutDir` from wiping arbitrary locations (defense-in-depth on\n * top of cleanOutDir's runtime EC-3 guard).\n */\n distDir: z\n .string()\n .default('.theokit')\n .refine(\n (d) => !/^([A-Za-z]:)?[/\\\\]/.test(d) && !d.startsWith('..'),\n 'distDir must be a relative path inside the project root (e.g., \".theokit\")',\n ),\n /**\n * Agent runtime configuration.\n *\n * - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale\n * `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's\n * native registry GC but kept for backward-compat.\n * - `registry` (Phase 6, Production-Readiness #2): forwarded to\n * `Agent.registry.configure()` lazily on first request.\n * * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations\n * (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts\n * mid-stream → SDK aborts with code:'aborted'. Default 100 covers\n * indie/small-team; tune up for high-traffic.\n * * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables\n * idle eviction (LRU only).\n */\n agents: z\n .object({\n maxRegistries: z.number().int().positive().default(100),\n registry: z\n .object({\n /** LRU cap — MUST be ≥ max-concurrent-active-conversations. */\n maxAgents: z.number().int().positive().max(10_000).default(100),\n /** Idle eviction window in ms. 0 disables idle eviction (LRU only). */\n idleTimeoutMs: z\n .number()\n .int()\n .nonnegative()\n .default(30 * 60_000),\n })\n .optional(),\n })\n .optional(),\n port: z.number().int().min(1).max(65535).default(3000),\n /** Listen on all addresses (0.0.0.0) for LAN/mobile testing. */\n host: z.union([z.string(), z.boolean()]).default('localhost'),\n /** Automatically open browser on `theokit dev`. */\n open: z.union([z.boolean(), z.string()]).default(false),\n /** Exit if port is already in use instead of trying next available. */\n strictPort: z.boolean().default(false),\n /** Forward browser console/errors to terminal — useful for AI agent dev. */\n forwardConsole: z\n .union([\n z.boolean(),\n z.object({\n unhandledErrors: z.boolean().optional(),\n logLevels: z.array(z.enum(['error', 'warn', 'info', 'log', 'debug'])).optional(),\n }),\n ])\n .default(false),\n /** Pre-transform files on startup for faster first page load. */\n warmup: z\n .object({\n clientFiles: z.array(z.string()).optional(),\n ssrFiles: z.array(z.string()).optional(),\n })\n .optional(),\n ssr: z.boolean().default(false),\n /** When true (and ssr === true), use renderToPipeableStream with progressive\n * shell flush instead of single-shot renderToString. Opt-in for streaming\n * SSR; default false preserves the current behavior. */\n ssrStreaming: z.boolean().default(false),\n rateLimit: rateLimitSchema.optional(),\n upload: uploadSchema.optional(),\n logging: loggingSchema.optional(),\n security: securitySchema.optional(),\n serialization: z.enum(['json', 'superjson']).default('json'),\n // Plugins are validated structurally at runtime by createPluginRunnerFromConfig.\n // Zod only checks the shape minimally (must be array). Type-level safety is\n // provided through defineConfig at the user surface.\n plugins: z.array(z.unknown()).optional(),\n /** Enable client-side batching of theoFetch calls and the\n * /api/__theo_batch__ server endpoint. */\n batching: z\n .union([z.boolean(), z.object({ max: z.number().int().positive().optional() })])\n .optional(),\n /** T4.1 — Audit log. When `logger` is provided, framework events\n * (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are\n * emitted to it. Default: noop. */\n audit: z\n .object({\n logger: z.unknown().optional(),\n })\n .optional(),\n /** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;\n * undefined = enabled when @theokit/ui is detected in node_modules. */\n ui: z\n .union([\n z.literal(false),\n z.object({\n theme: z.enum(['violet-forge', 'noir', 'paper']).optional(),\n fonts: z.enum(['bundled', 'cdn']).optional(),\n }),\n ])\n .optional(),\n /**\n * Cache subsystem (caching-and-revalidation-plan).\n * Default `undefined` keeps caching disabled (backward compatible).\n * Pass `cache: {}` to opt in with defaults.\n */\n cache: cacheSchema.optional(),\n /**\n * Devtools overlay (Phase 0.4.0+ — see docs/plans/devtools-plan.md).\n *\n * - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.\n * - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).\n * - `{ ... }`: devtools enabled with explicit defaults (position, theme).\n *\n * Tree-shaken to noop in prod via the dual-export pattern in\n * `packages/theo/src/devtools/index.ts` (EC-17 positive prod check).\n */\n devtools: z\n .union([\n z.literal(false),\n z.object({\n position: z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']).optional(),\n theme: z.enum(['light', 'dark', 'system']).optional(),\n }),\n ])\n .optional(),\n /**\n * Informative list of deploy adapters the app supports. Does NOT\n * trigger per-build translations — only the `--target` CLI flag does\n * (EC-201 / ADR D2). If `config.adapters` includes a target NOT\n * matching `--target`, `theokit build` emits a cross-reference note.\n */\n adapters: z.array(z.string()).optional(),\n /**\n * Extra package names to pre-bundle via Vite's `optimizeDeps.include`.\n *\n * Framework auto-includes `@theokit/ui` and `lucide-react` when present.\n * Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`\n * (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers\n * installing `mermaid` for Mermaid SVG rendering — must declare those\n * here so Vite can resolve the literal in dev mode without\n * \"Failed to resolve module specifier\" errors. Production builds use\n * tsup/esbuild bundling and are not affected.\n *\n * Example:\n * viteOptimizeDeps: ['mermaid']\n */\n viteOptimizeDeps: z.array(z.string()).optional(),\n /**\n * Jobs backend (ADR D3 + T2.1). When configured, every request gets\n * `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a\n * `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).\n */\n jobs: z\n .object({\n backend: z.custom<unknown>((v) => typeof v === 'object' && v !== null),\n })\n .optional(),\n /**\n * StorageManager configuration (T1.1 / ADR-0007).\n *\n * Declares Postgres servers + databases + Redis servers. Consumed by\n * `getStorageManager().configure()` at boot via `start.ts`.\n *\n * Default `undefined` means manager exists but is not configured — adapters\n * relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will\n * throw with an actionable error on first use.\n */\n storage: storageSchema.optional(),\n /**\n * Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).\n *\n * Declarative external sidecar processes (Python FastAPI / Node Hono)\n * that boot alongside the TheoKit TS app. Empty `services: {}` is the\n * default and preserves Wave 1 behavior.\n */\n services: servicesConfigSchema,\n /**\n * G2 — OpenAPI emit (build-time only).\n *\n * When present, `theokit build` writes a generated `openapi.json` from\n * every `defineRoute()` body/query/params Zod schema. `undefined` keeps\n * the framework backward-compatible (no emit). Pass `openapi: {}` to\n * opt in with defaults.\n *\n * Defaults: spec 3.1.0 · servers `http://localhost:3000` · title\n * \"TheoKit App\" · version \"0.0.0\" · outDir \".theokit\" (next to dist).\n *\n * The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides\n * `servers[].url` at emit time without rebuilding the config.\n */\n openapi: z\n .object({\n servers: z\n .array(\n z.object({\n url: z.url(),\n description: z.string().optional(),\n }),\n )\n .default([{ url: 'http://localhost:3000', description: 'Local development' }]),\n specVersion: z.enum(['3.1.0', '3.0.3']).default('3.1.0'),\n title: z.string().default('TheoKit App'),\n version: z.string().default('0.0.0'),\n outDir: z.string().default('.theokit'),\n })\n .optional(),\n /**\n * G5 T1.3 — error envelope transformer hook (blueprint ADR D3).\n *\n * When present, runs at framework error-boundary time to enrich the\n * envelope with consumer-defined extensions (`hint`, custom telemetry\n * tags, etc.) BEFORE the envelope is serialized to the client. The\n * function signature is preserved via the explicit `FormatErrorHook`\n * type — TS inference flows from `theo.config.ts` into `@theo/client`\n * codegen and `@theokit/ui` AgentErrorCard consumers.\n *\n * Use `z.custom<FormatErrorHook>(...)` because Zod's built-in\n * `z.function()` discards its TS signature after parse; `z.custom`\n * with a function-typed runtime guard preserves the call-site type\n * without trading off Zod runtime validation.\n */\n formatError: z\n .custom<FormatErrorHook>((val) => typeof val === 'function', {\n message: 'formatError must be a function',\n })\n .optional(),\n })\n // EC-2 fix: cross-config refine — no service may share TheoKit's web port.\n .refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {\n message: 'service.port collides with TheoKit web port — change one of them',\n path: ['services'],\n })\n\nexport type TheoConfig = z.infer<typeof theoConfigSchema>\n\nexport type OpenApiConfig = NonNullable<TheoConfig['openapi']>\n\n// Silence unused-import warnings for type-only re-exports — TS strips at compile,\n// but exports above re-introduce them for downstream consumers.\nexport type _FormatErrorContext = FormatErrorContext\n","import { z } from 'zod'\n\n/**\n * EC-3 — CWE-113 HTTP Response Splitting mitigation.\n *\n * Every string that becomes a header value must reject CR/LF. Apps that\n * derive header values from untrusted input (feature flags, tenant config,\n * URL params) would otherwise let attackers inject Set-Cookie / Location\n * headers via `\\r\\n`. Apply this refinement to every header-bound string\n * field: permissionsPolicy, csp, hsts, referrerPolicy, CORS allow/expose.\n */\nexport const headerSafeString = z\n .string()\n .refine((s) => !/[\\r\\n]/.test(s), { message: 'Header value must not contain CR/LF' })\n","import { z } from 'zod'\n\n/**\n * Base bucket config — legacy shape preserved for backwards compatibility.\n */\nconst baseRateLimitSchema = z.object({\n windowMs: z.number().min(1),\n max: z.number().int().min(1),\n})\n\n/**\n * T2.2 — Per-route + per-user rate limit. The new shape adds `routes`\n * (path map), `keyBy`, and `cookieName`. The legacy flat shape is still\n * accepted via the union — see `createRouteRateLimiter` for normalization.\n */\nexport const rateLimitSchema = z.union([\n baseRateLimitSchema,\n z.object({\n default: baseRateLimitSchema.optional(),\n routes: z.record(z.string(), baseRateLimitSchema).optional(),\n keyBy: z\n .union([\n z.enum(['ip', 'session', 'user']),\n z.function({ input: z.tuple([z.unknown()]), output: z.string() }),\n ])\n .optional(),\n cookieName: z.string().min(1).optional(),\n }),\n])\n","import { z } from 'zod'\n\nexport const uploadSchema = z.object({\n maxFileSize: z\n .number()\n .min(1)\n .default(10 * 1024 * 1024), // 10MB\n maxFiles: z.number().int().min(1).default(10),\n maxFieldSize: z\n .number()\n .min(1)\n .default(1 * 1024 * 1024), // 1MB\n})\n","import { z } from 'zod'\n\nexport const loggingSchema = z.object({\n level: z.enum(['debug', 'info', 'warn', 'error', 'silent']).default('info'),\n})\n","import { z } from 'zod'\n\n/**\n * Cache subsystem config (caching-and-revalidation-plan).\n * Default `cache: undefined` keeps the framework backward-compatible\n * (no engine initialized → no cache primitives available).\n *\n * Setting `cache: {}` opts in with all defaults.\n */\nconst routeRuleSchema = z.object({\n maxAge: z.number().nonnegative().finite().optional(),\n swr: z.number().nonnegative().finite().optional(),\n tags: z.array(z.string()).optional(),\n})\n\nexport const cacheSchema = z.object({\n enabled: z.boolean().default(true),\n /** 'memory' uses InMemoryCacheAdapter; otherwise pass a custom adapter instance. */\n storage: z.union([z.literal('memory'), z.custom<unknown>()]).default('memory'),\n maxEntries: z.number().int().positive().default(1000),\n defaults: z\n .object({\n maxAge: z.number().nonnegative().finite().default(1),\n swr: z.number().nonnegative().finite().optional(),\n cacheErrors: z.boolean().default(false),\n })\n .default({ maxAge: 1, cacheErrors: false }),\n keyDerivation: z\n .object({\n excludeQuery: z.array(z.string()).optional(),\n sortQuery: z.boolean().default(true),\n lowercaseHost: z.boolean().default(true),\n })\n .default({ sortQuery: true, lowercaseHost: true }),\n routeRules: z.record(z.string(), routeRuleSchema).optional(),\n})\n","import { z } from 'zod'\n\nimport { headerSafeString } from './header-safe.js'\n\n/**\n * Phase 5 — CSRF warn-first (EC-1).\n *\n * 0.2.0 default: `warn`. Existing apps keep working but get structured\n * warnings about every state-mutating request that does not carry the\n * `X-Theo-Action: 1` header. 0.3.0 will flip the default to `strict`.\n *\n * Set explicitly to `strict` to opt into the future default early,\n * or `off` to disable CSRF entirely (only valid when you have another\n * defense — bearer auth, no session cookies, etc).\n */\n\n/**\n * Phase 6 — Default security headers (D4 / EC-2).\n *\n * 0.2.0 defaults:\n * - CSP in `report-only` mode (EC-2: don't break existing apps)\n * - X-Frame-Options: DENY · X-Content-Type-Options: nosniff\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - HSTS in production only (no TLS on localhost)\n *\n * Users override individual headers, swap CSP to `enforce`, or disable\n * CSP entirely (`csp: false` / `cspMode: 'off'`).\n */\nexport const securityHeadersSchema = z.object({\n csp: z.union([headerSafeString, z.literal(false)]).optional(),\n // T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.\n // Users who want the old behaviour set `cspMode: 'report-only'`\n // explicitly. See docs/migrating/0.2-to-0.3.md.\n cspMode: z.enum(['enforce', 'report-only', 'off']).default('enforce'),\n hsts: z.union([headerSafeString, z.literal(false)]).optional(),\n frameOptions: z.enum(['DENY', 'SAMEORIGIN']).default('DENY'),\n contentTypeOptions: z.literal('nosniff').default('nosniff'),\n referrerPolicy: headerSafeString.default('strict-origin-when-cross-origin'),\n /**\n * T1.1 — Permissions-Policy directive string. EC-3-refined: rejects CR/LF.\n * Pass `false` to suppress the header.\n */\n permissionsPolicy: z.union([headerSafeString, z.literal(false)]).optional(),\n})\n\n/**\n * T5.1 — Disallowed-routes escalation pattern (Rails-inspired).\n *\n * `routes` accepts string (exact match — trailing slash matters) or\n * RegExp entries. Matched routes that would otherwise emit `csrf.warn`\n * dispatch through `disallowedBehavior` instead:\n * - `'warn'` : no-op vs the default warn-mode behavior\n * - `'raise'` : escalate to 403, even when global `csrf` mode is 'warn'\n *\n * Use to roll out strict mode per-route (e.g., flip /api/auth/* first)\n * without committing the entire surface to strict at once.\n */\nexport const disallowedConfigSchema = z.object({\n routes: z.array(z.union([z.string(), z.instanceof(RegExp)])),\n behavior: z.enum(['warn', 'raise']).default('raise'),\n})\n\n/**\n * T1.2 — CORS configuration.\n *\n * `origins` accepts a single value (`'*'`, string, RegExp, callback) OR an\n * array of (string | RegExp). The spec-violating `origins: '*'` +\n * `credentials: true` combination is rejected at parse time (browsers\n * ignore wildcards when credentials are sent).\n *\n * EC-3 — `allowedHeaders` and `exposedHeaders` entries go through the\n * header-safe refinement (CR/LF rejected — CWE-113 mitigation).\n */\nexport const corsSchema = z\n .object({\n origins: z.union([\n z.literal('*'),\n headerSafeString,\n z.instanceof(RegExp),\n z.array(z.union([headerSafeString, z.instanceof(RegExp)])),\n z.function({ input: z.tuple([z.string()]), output: z.boolean() }),\n ]),\n methods: z\n .array(z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']))\n .optional(),\n allowedHeaders: z.array(headerSafeString).optional(),\n exposedHeaders: z.array(headerSafeString).optional(),\n credentials: z.boolean().default(false),\n maxAge: z.number().int().min(0).max(86400).default(600),\n })\n .refine((c) => !(c.origins === '*' && c.credentials), {\n message: 'CORS spec forbids origins:\"*\" with credentials:true — browsers ignore the wildcard',\n })\n\nexport const securitySchema = z.object({\n // T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that\n // grep their warn-mode logs from 0.2.x already know which endpoints\n // break; opt back into 'warn' globally OR use disallowedRoutes for\n // surgical migration. See docs/migrating/0.2-to-0.3.md.\n csrf: z.enum(['off', 'warn', 'strict']).default('strict'),\n headers: securityHeadersSchema.optional(),\n /** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */\n disallowed: disallowedConfigSchema.optional(),\n /** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */\n cors: corsSchema.optional(),\n})\n","import { z } from 'zod'\n\n/**\n * StorageManager schemas (T1.1 / ADR-0007 D4).\n *\n * `theo.config.ts > storage` declares Postgres servers (credentials), the\n * databases on each server (TheoCloud / managed PG / self-host), and Redis\n * servers. The runtime `StorageManager` consumes this block; adapters\n * (PostgresJobBackend, PostgresConversationStorage, etc.) request pools by\n * database name and never see raw credentials at the call site.\n *\n * EC-1 (DOCUMENT in concept doc T4.1): Zod default strip-unknown mode\n * silently drops typo keys (`databasees` instead of `databases`). The\n * concept doc warns users to use exact names; runtime errors are still\n * actionable (`Database \"X\" not configured`).\n *\n * EC-2 (DEFERRED to use-time): `databases.X.server` references a key in\n * `servers`. Cross-field validation is intentionally NOT enforced at parse\n * time — `StorageManager.usePostgres()` throws an actionable error on the\n * first call with the dangling reference, matching Rails / Nitro behavior.\n */\nconst tlsConfigSchema = z.object({\n rejectUnauthorized: z.boolean().optional(),\n caCert: z.string().optional(),\n clientCert: z.string().optional(),\n clientKey: z.string().optional(),\n})\n\nconst serverConfigSchema = z.object({\n host: z.string().min(1),\n port: z.number().int().positive().max(65535).optional(),\n user: z.string().min(1),\n /** Password may be the empty string (some providers permit it). */\n password: z.string(),\n tls: tlsConfigSchema.optional(),\n})\n\nconst postgresPoolConfigSchema = z.object({\n min: z.number().int().nonnegative().optional(),\n max: z.number().int().positive().optional(),\n connectionTimeoutMillis: z.number().int().positive().optional(),\n idleTimeoutMillis: z.number().int().nonnegative().optional(),\n})\n\nconst postgresDatabaseConfigSchema = z.object({\n /** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */\n server: z.string().min(1),\n database: z.string().min(1),\n pool: postgresPoolConfigSchema.optional(),\n})\n\nconst redisServerConfigSchema = serverConfigSchema.extend({\n db: z.number().int().nonnegative().optional(),\n maxRetriesPerRequest: z.number().int().nonnegative().optional(),\n})\n\nexport const storageSchema = z.object({\n servers: z.record(z.string(), serverConfigSchema).optional(),\n databases: z.record(z.string(), postgresDatabaseConfigSchema).optional(),\n redis: z.record(z.string(), redisServerConfigSchema).optional(),\n})\n\nexport type StorageConfig = z.infer<typeof storageSchema>\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/jobs/define-job.ts","../src/server/jobs/job-backend.ts","../src/server/jobs/job-backend-memory.ts","../src/server/jobs/job-backend-postgres.ts","../src/server/jobs/job-runner.ts","../src/server/jobs/job-manifest.ts","../src/server/jobs/job-scan.ts"],"sourcesContent":["import type { JobDefinition, JobOptions } from './job-types.js'\n\nconst NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/\n\nfunction validateName(name: string): void {\n if (typeof name !== 'string' || !NAME_RE.test(name)) {\n throw new Error(\n `defineJob: invalid name \"${name}\". ` +\n 'Must be 1-64 chars, lowercase alphanumeric + hyphen, starting with [a-z0-9].',\n )\n }\n}\n\n/**\n * Declare a background job. Pure identity helper — no registration side\n * effect; the build-time scanner (T2.3) discovers definitions by walking\n * `server/jobs/` and emits `.theokit/jobs.json`.\n *\n * Per ADR-0003, handler returns `void` (or `Promise<void>`); no workflow\n * API. To run another job after this one, call `ctx.queue.enqueue` from\n * the handler.\n *\n * @example\n * ```ts\n * // server/jobs/process-document.ts\n * import { defineJob } from 'theokit/server'\n * import { z } from 'zod'\n *\n * export default defineJob('process-document', {\n * input: z.object({ documentId: z.string() }),\n * maxAttempts: 3,\n * async handler({ input, traceId }) {\n * // process input.documentId\n * },\n * })\n * ```\n */\nexport function defineJob<TInput = unknown>(\n name: string,\n options: JobOptions<TInput>,\n): JobDefinition<TInput> {\n validateName(name)\n return {\n name,\n maxAttempts: options.maxAttempts ?? 1,\n hasInputSchema: options.input !== undefined,\n handler: options.handler,\n inputSchema: options.input,\n }\n}\n","/**\n * Neutral `JobBackend` interface per ADR-0002.\n *\n * Implementations ship in core:\n * - `InMemoryJobBackend` (T2.2) — dev + tests; no external deps\n * - `PostgresJobBackend` (T3.1) — production; requires `pg` peer dep\n *\n * Community packages (`@theokit/jobs-redis`, `@theokit/jobs-sqs`, etc.)\n * implement this same interface to plug in alternate substrates without\n * modifying TheoKit core.\n */\n\nexport interface JobEnqueueInput {\n /** The job name (matches a `defineJob` declaration). */\n name: string\n /** Validated input (already passed through Zod or equivalent at the queue-client layer). */\n input: unknown\n /** Optional idempotency key for at-most-once dispatch within TTL. */\n idempotencyKey?: string\n /** Optional delay before the job becomes available for dispatch. */\n delaySeconds?: number\n /** W3C Trace Context propagation per ADR/R0.5.9. */\n traceparent?: string\n /** Maximum dispatch attempts (read from JobDefinition by the queue client). */\n maxAttempts?: number\n}\n\nexport interface JobLease {\n readonly jobId: string\n readonly name: string\n readonly input: unknown\n readonly attempts: number\n readonly maxAttempts: number\n readonly traceparent?: string\n readonly lockExpiresAt: Date\n}\n\nexport interface JobBackend {\n /** Human-readable name for logging (e.g., \"memory\", \"postgres\"). */\n readonly name: string\n\n /** Persist a job for later dispatch. Returns the generated jobId. */\n enqueue(input: JobEnqueueInput): Promise<{ jobId: string }>\n\n /** Worker loop polls for the next available leases. */\n dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]>\n\n /** Mark a job complete (success). */\n ack(jobId: string): Promise<void>\n\n /** Mark a job failed; backend decides retry vs DLQ via attempts. */\n nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void>\n\n /** Optional: return existing jobId if `key` was enqueued within `ttlSeconds`. */\n idempotency?(key: string, ttlSeconds: number): Promise<{ jobId: string } | null>\n}\n\n/**\n * Thrown from a job handler to opt out of retry policy. The backend MUST\n * nack with `nonRetryable: true` and permanently remove the job.\n */\nexport class NonRetryableError extends Error {\n readonly code = 'NON_RETRYABLE'\n constructor(message: string) {\n super(message)\n this.name = 'NonRetryableError'\n }\n}\n","// T5a.1a — Web Standards migration (leaf-first slice). Web Crypto's\n// `crypto.randomUUID()` is available on globalThis in every supported\n// runtime per ADR-0028 (Node 22+, CF Workers, Bun, Deno, browsers).\n// No node:crypto import needed.\n\nimport type { JobBackend, JobEnqueueInput, JobLease } from './job-backend.js'\n\n/**\n * In-memory job backend for dev + tests + single-instance prototypes.\n *\n * Zero external dependencies. Storage is two Maps:\n * - `pending: Map<jobId, PendingEntry>` — all enqueued jobs (locked or not)\n * - `idempotencyMap: Map<key, { jobId; expiresAt }>` — dedup window\n *\n * Per ADR-0002: this is one of two first-party backends shipped in core.\n * Production deploys plug in PostgresJobBackend (T3.1).\n *\n * EC-104 (jobs-crons-webhooks-cost-tracking-plan): registers a\n * `process.on('beforeExit')` handler that clears pending dispatch timers\n * and logs a warning with the dropped count. Failure mode is visible,\n * not silent. `destroy()` removes the listener for test isolation.\n */\n\nexport interface InMemoryJobBackendOptions {\n /** Maximum pending entries before oldest is dropped + warning logged. */\n maxPending?: number\n}\n\ninterface PendingEntry {\n jobId: string\n name: string\n input: unknown\n attempts: number\n maxAttempts: number\n traceparent?: string\n availableAt: number\n lockExpiresAt: number | null\n dispatchTimer: NodeJS.Timeout | null\n}\n\ninterface IdempotencyEntry {\n jobId: string\n expiresAt: number\n}\n\nconst DEFAULT_MAX_PENDING = 10_000\n\nexport class InMemoryJobBackend implements JobBackend {\n readonly name = 'memory'\n readonly #pending = new Map<string, PendingEntry>()\n readonly #idempotency = new Map<string, IdempotencyEntry>()\n readonly #maxPending: number\n #beforeExitHandler: (() => void) | null = null\n #destroyed = false\n\n constructor(opts: InMemoryJobBackendOptions = {}) {\n this.#maxPending = opts.maxPending ?? DEFAULT_MAX_PENDING\n // EC-104 — register graceful shutdown handler. We keep a reference\n // so `destroy()` can remove it (test isolation; avoid leaking\n // listeners across vitest test files).\n this.#beforeExitHandler = (): void => {\n this.#cleanupOnShutdown()\n }\n process.on('beforeExit', this.#beforeExitHandler)\n }\n\n async enqueue(input: JobEnqueueInput): Promise<{ jobId: string }> {\n // Overflow eviction (visible failure, not silent leak)\n if (this.#pending.size >= this.#maxPending) {\n const oldestKey = this.#pending.keys().next().value\n if (oldestKey) {\n const oldest = this.#pending.get(oldestKey)\n if (oldest?.dispatchTimer) clearTimeout(oldest.dispatchTimer)\n this.#pending.delete(oldestKey)\n }\n\n console.warn(\n `[theokit:jobs:memory] pending overflow at ${this.#maxPending} — dropped oldest. ` +\n 'Switch to PostgresJobBackend for production.',\n )\n }\n\n // Idempotency dedup\n if (input.idempotencyKey) {\n const existing = this.#idempotency.get(input.idempotencyKey)\n const now = Date.now()\n if (existing && existing.expiresAt > now) {\n return { jobId: existing.jobId }\n }\n }\n\n const jobId = globalThis.crypto.randomUUID()\n const now = Date.now()\n const availableAt = now + (input.delaySeconds ?? 0) * 1000\n const entry: PendingEntry = {\n jobId,\n name: input.name,\n input: input.input,\n attempts: 0,\n maxAttempts: input.maxAttempts ?? 1,\n traceparent: input.traceparent,\n availableAt,\n lockExpiresAt: null,\n dispatchTimer: null,\n }\n this.#pending.set(jobId, entry)\n\n if (input.idempotencyKey) {\n // Default TTL window matches typical idempotency expectations;\n // explicit idempotency(key, ttl) check overrides per caller.\n this.#idempotency.set(input.idempotencyKey, {\n jobId,\n expiresAt: now + 60_000,\n })\n }\n\n return Promise.resolve({ jobId })\n }\n\n async dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]> {\n const batchSize = opts.batchSize ?? 1\n const lockSeconds = opts.lockSeconds ?? 30\n const now = Date.now()\n\n const leases: JobLease[] = []\n for (const entry of this.#pending.values()) {\n if (leases.length >= batchSize) break\n // Skip if locked + lock still valid\n if (entry.lockExpiresAt !== null && entry.lockExpiresAt > now) continue\n // Skip if not yet available (delay)\n if (entry.availableAt > now) continue\n\n entry.lockExpiresAt = now + lockSeconds * 1000\n entry.attempts += 1\n leases.push({\n jobId: entry.jobId,\n name: entry.name,\n input: entry.input,\n attempts: entry.attempts,\n maxAttempts: entry.maxAttempts,\n traceparent: entry.traceparent,\n lockExpiresAt: new Date(entry.lockExpiresAt),\n })\n }\n return Promise.resolve(leases)\n }\n\n async ack(jobId: string): Promise<void> {\n const entry = this.#pending.get(jobId)\n if (entry?.dispatchTimer) clearTimeout(entry.dispatchTimer)\n this.#pending.delete(jobId)\n return Promise.resolve()\n }\n\n async nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void> {\n const entry = this.#pending.get(jobId)\n if (!entry) return Promise.resolve()\n if (opts.nonRetryable || entry.attempts >= entry.maxAttempts) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n this.#pending.delete(jobId)\n } else {\n // Release lock — becomes available again\n entry.lockExpiresAt = null\n }\n return Promise.resolve()\n }\n\n idempotency(key: string, ttlSeconds: number): Promise<{ jobId: string } | null> {\n const now = Date.now()\n const existing = this.#idempotency.get(key)\n if (!existing || existing.expiresAt <= now) {\n return Promise.resolve(null)\n }\n // Renew if caller's ttl differs\n existing.expiresAt = Math.max(existing.expiresAt, now + ttlSeconds * 1000)\n return Promise.resolve({ jobId: existing.jobId })\n }\n\n /**\n * Remove the beforeExit listener and clear all pending timers.\n * Idempotent — safe to call multiple times. Use in test teardown to\n * avoid leaking listeners across vitest test files.\n */\n destroy(): void {\n if (this.#destroyed) return\n this.#destroyed = true\n if (this.#beforeExitHandler) {\n process.off('beforeExit', this.#beforeExitHandler)\n this.#beforeExitHandler = null\n }\n for (const entry of this.#pending.values()) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n }\n }\n\n /** EC-104 test hook — invokes the shutdown handler synchronously. */\n triggerBeforeExitForTest(): void {\n this.#cleanupOnShutdown()\n }\n\n #cleanupOnShutdown(): void {\n const droppedCount = this.#pending.size\n for (const entry of this.#pending.values()) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n }\n if (droppedCount > 0) {\n console.warn(\n `[theokit:jobs:memory] ${droppedCount} jobs dropped on shutdown — ` +\n 'use PostgresJobBackend for durability across restarts.',\n )\n }\n }\n}\n","import type { PostgresFactory, StorageManager } from '../storage/storage-manager.js'\nimport type { PoolLike } from '../storage/storage-types.js'\n\nimport type { JobBackend, JobEnqueueInput, JobLease } from './job-backend.js'\n\n/**\n * Postgres-backed `JobBackend` per ADR-0002 (T3.1).\n *\n * Uses the Graphile Worker pattern:\n * - Single `theokit_jobs` table.\n * - Dequeue via `SELECT ... FOR UPDATE SKIP LOCKED` (concurrent workers\n * never race for the same job).\n * - Idempotency via UNIQUE INDEX on (name, idempotency_key).\n *\n * `pg` is an OPTIONAL peer dependency — the caller provides their own\n * `pg.Pool` (or compatible). This avoids forcing every TheoKit user to\n * install Postgres tooling for in-memory dev work.\n *\n * Pool size recommendation (EC-108): set `connectionTimeoutMillis` to\n * a finite value (e.g., 5000) so a saturated pool errors instead of\n * deadlocking. Suggested pool size: `min(workerConcurrency * 1.5, 20)`.\n *\n * @see https://github.com/graphile/worker (Postgres SKIP LOCKED reference)\n * @see docs/adr/0002-job-backend-interface-neutral-contract.md\n */\n\n/**\n * `PoolLike` moved to `../storage/storage-types.ts` (T0.2 / ADR-0007 D7) to\n * deduplicate the shape across multiple adapters. Re-exported here so existing\n * `import { PoolLike } from 'theokit/server'` (which routes through the jobs\n * barrel today) continues to compile unchanged.\n */\nexport type { PoolLike } from '../storage/storage-types.js'\n\nexport interface PostgresJobBackendOptions {\n pool: PoolLike\n /** Table name override (default `theokit_jobs`). */\n tableName?: string\n}\n\nconst DEFAULT_TABLE = 'theokit_jobs'\n\nexport class PostgresJobBackend implements JobBackend {\n readonly name = 'postgres'\n readonly #pool: PoolLike\n readonly #table: string\n\n constructor(opts: PostgresJobBackendOptions) {\n // Runtime guard for users who pass `pool: undefined` despite the type;\n // helps surface \"you forgot to install pg\" early with an actionable\n // error rather than a cryptic TypeError later.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!opts.pool) {\n throw new Error(\n 'PostgresJobBackend requires a `pool` option (pg.Pool or compatible). ' +\n 'Install pg via `pnpm add pg` and pass a configured Pool instance.',\n )\n }\n this.#pool = opts.pool\n this.#table = opts.tableName ?? DEFAULT_TABLE\n }\n\n /**\n * T2.1 — Construct a `PostgresJobBackend` using a pool managed by the\n * shared `StorageManager`. The manager caches the pool per `dbName`, so\n * repeated calls (in the same process) re-use the same connection pool.\n *\n * Equivalent to:\n * ```ts\n * const pool = manager.usePostgres(dbName, factory)\n * new PostgresJobBackend({ pool, ...options })\n * ```\n *\n * Errors thrown by the manager (unknown dbName, dangling server reference,\n * manager already disposed) propagate to the caller with actionable text.\n *\n * @see ADR-0007\n */\n static fromStorageManager(\n manager: StorageManager,\n dbName: string,\n factory: PostgresFactory,\n options?: Omit<PostgresJobBackendOptions, 'pool'>,\n ): PostgresJobBackend {\n const pool = manager.usePostgres(dbName, factory)\n return new PostgresJobBackend({ pool, ...options })\n }\n\n /**\n * Create or upgrade the schema. Idempotent (uses IF NOT EXISTS).\n * Call once at process start; safe to re-run on every deploy.\n */\n async migrate(): Promise<void> {\n const t = this.#table\n await this.#pool.query(`\n CREATE TABLE IF NOT EXISTS ${t} (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n input JSONB NOT NULL,\n attempts INT NOT NULL DEFAULT 0,\n max_attempts INT NOT NULL DEFAULT 1,\n available_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n locked_until TIMESTAMPTZ,\n traceparent TEXT,\n idempotency_key TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n )\n `)\n await this.#pool.query(`\n CREATE UNIQUE INDEX IF NOT EXISTS ${t}_idempotency_key_idx\n ON ${t} (name, idempotency_key)\n WHERE idempotency_key IS NOT NULL\n `)\n await this.#pool.query(`\n CREATE INDEX IF NOT EXISTS ${t}_available_at_idx\n ON ${t} (available_at)\n WHERE locked_until IS NULL\n `)\n }\n\n async enqueue(input: JobEnqueueInput): Promise<{ jobId: string }> {\n const id = randomId()\n const delaySeconds = input.delaySeconds ?? 0\n const t = this.#table\n\n // Idempotency: explicit SELECT-then-INSERT. The UNIQUE INDEX is the\n // safety net under race conditions (we surface the conflict by\n // re-selecting on insert-error). This 2-step path is pg-mem compatible\n // AND works on real Postgres.\n if (input.idempotencyKey) {\n const existing = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${t} WHERE name = $1 AND idempotency_key = $2 LIMIT 1`,\n [input.name, input.idempotencyKey],\n )\n if (existing.rows.length > 0) {\n return { jobId: existing.rows[0].id }\n }\n }\n\n await this.#pool.query(\n `INSERT INTO ${t} (id, name, input, max_attempts, available_at, traceparent, idempotency_key)\n VALUES ($1, $2, $3::jsonb, $4, NOW() + ($5 || ' seconds')::interval, $6, $7)`,\n [\n id,\n input.name,\n JSON.stringify(input.input),\n input.maxAttempts ?? 1,\n delaySeconds.toString(),\n input.traceparent ?? null,\n input.idempotencyKey ?? null,\n ],\n )\n return { jobId: id }\n }\n\n async dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]> {\n const t = this.#table\n const batchSize = opts.batchSize ?? 1\n const lockSeconds = opts.lockSeconds ?? 30\n\n // Two-step pattern (pg-mem compatible AND real-Postgres compatible):\n // 1. SELECT FOR UPDATE SKIP LOCKED to claim candidate IDs.\n // 2. UPDATE in-place to mark lock_until + bump attempts.\n // On real Postgres, the FOR UPDATE SKIP LOCKED prevents concurrent\n // workers from seeing the same rows. On pg-mem the lock is silently\n // ignored, but pg-mem serializes all queries through one in-memory\n // db so the test still verifies single-claim semantics.\n const candidates = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${t}\n WHERE (locked_until IS NULL OR locked_until < NOW())\n AND available_at <= NOW()\n ORDER BY available_at ASC\n LIMIT $1\n FOR UPDATE SKIP LOCKED`,\n [batchSize],\n )\n\n if (candidates.rows.length === 0) return []\n\n const ids = candidates.rows.map((r) => r.id)\n // Build a parameterized IN clause: ($2, $3, ...). pg-mem's UPDATE\n // does not support `ANY($1::text[])`, so we materialize the list.\n // For batch sizes we expect (typically ≤100), this is fine.\n const placeholders = ids.map((_, i) => `$${i + 2}`).join(', ')\n\n const r = await this.#pool.query<{\n id: string\n name: string\n input: unknown\n attempts: number\n max_attempts: number\n traceparent: string | null\n locked_until: Date | string\n }>(\n `UPDATE ${t}\n SET locked_until = NOW() + ($1 || ' seconds')::interval,\n attempts = attempts + 1\n WHERE id IN (${placeholders})\n RETURNING id, name, input, attempts, max_attempts, traceparent, locked_until`,\n [lockSeconds.toString(), ...ids],\n )\n\n return r.rows.map((row) => ({\n jobId: row.id,\n name: row.name,\n input: row.input,\n attempts: row.attempts,\n maxAttempts: row.max_attempts,\n traceparent: row.traceparent ?? undefined,\n lockExpiresAt:\n row.locked_until instanceof Date ? row.locked_until : new Date(row.locked_until),\n }))\n }\n\n async ack(jobId: string): Promise<void> {\n await this.#pool.query(`DELETE FROM ${this.#table} WHERE id = $1`, [jobId])\n }\n\n async nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void> {\n const t = this.#table\n if (opts.nonRetryable) {\n await this.#pool.query(`DELETE FROM ${t} WHERE id = $1`, [jobId])\n return\n }\n // Check attempts >= maxAttempts → permanent removal.\n const r = await this.#pool.query<{\n attempts: number\n max_attempts: number\n }>(`SELECT attempts, max_attempts FROM ${t} WHERE id = $1`, [jobId])\n if (r.rows.length === 0) return\n const { attempts, max_attempts } = r.rows[0]\n if (attempts >= max_attempts) {\n await this.#pool.query(`DELETE FROM ${t} WHERE id = $1`, [jobId])\n } else {\n // Release lock — next dequeue picks it up again.\n await this.#pool.query(`UPDATE ${t} SET locked_until = NULL WHERE id = $1`, [jobId])\n }\n }\n\n async idempotency(key: string, _ttlSeconds: number): Promise<{ jobId: string } | null> {\n // The UNIQUE INDEX is the source of truth. Look for any non-deleted\n // row with this idempotency_key.\n const r = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${this.#table} WHERE idempotency_key = $1 LIMIT 1`,\n [key],\n )\n return r.rows.length > 0 ? { jobId: r.rows[0].id } : null\n }\n}\n\nfunction randomId(): string {\n // Use crypto.randomUUID if available; this is process-local, not DB-generated.\n // The DB UNIQUE constraint is on (name, idempotency_key), not on id, so\n // collisions across processes are functionally impossible.\n return globalThis.crypto.randomUUID()\n}\n","import {\n extractTraceContext,\n generateNewTraceContext,\n} from '../observability/trace-context-propagation.js'\n\nimport { type JobBackend, type JobLease, NonRetryableError } from './job-backend.js'\nimport type { JobContext, JobDefinition } from './job-types.js'\n\n/**\n * Job runner — pulls leases from a backend and invokes the matching\n * handler. Each invocation wires up `JobContext` with:\n * - `traceId` extracted from the lease's `traceparent` header (W3C\n * Trace Context per R0.5.9), OR a fresh generated trace_id when\n * the upstream had no traceparent OR it was malformed.\n * - `attempt` from the lease.\n * - `signal` that aborts when the lease times out.\n *\n * `tick()` runs one dequeue+dispatch cycle and returns when all leases\n * have been ack'd or nack'd. Production deploys call `tick()` in a loop\n * with backoff (the loop is outside this module's scope).\n *\n * EC-6 (trace continuity edge): when a lease has no traceparent OR a\n * malformed one, the handler still gets a valid traceId (generated) —\n * never null/undefined. The job MAY itself enqueue child jobs that\n * continue the new trace_id.\n */\n\nexport interface JobRunner {\n /** Run one dequeue+dispatch cycle. */\n tick(opts?: { batchSize?: number; lockSeconds?: number }): Promise<number>\n}\n\nexport function createJobRunner(\n backend: JobBackend,\n definitions: readonly JobDefinition[],\n): JobRunner {\n const byName = new Map<string, JobDefinition>()\n for (const def of definitions) {\n byName.set(def.name, def)\n }\n\n const runOne = async (lease: JobLease): Promise<void> => {\n const def = byName.get(lease.name)\n if (!def) {\n // Job name not registered in this runner's definition set. Nack\n // permanently — the lease can't be processed by this runner.\n await backend.nack(lease.jobId, {\n error: `No job definition for \"${lease.name}\"`,\n nonRetryable: true,\n })\n return\n }\n\n // Resolve trace ID from lease's traceparent, or fall back to fresh.\n let traceId: string\n if (lease.traceparent) {\n const headers = new Headers({ traceparent: lease.traceparent })\n const ctx = extractTraceContext(headers)\n traceId = ctx?.trace_id ?? generateNewTraceContext().trace_id\n } else {\n traceId = generateNewTraceContext().trace_id\n }\n\n // Optional Zod validation on input.\n let parsedInput: unknown = lease.input\n if (def.inputSchema) {\n try {\n parsedInput = def.inputSchema.parse(lease.input)\n } catch (err) {\n await backend.nack(lease.jobId, {\n error: `Input validation failed: ${err instanceof Error ? err.message : String(err)}`,\n nonRetryable: true,\n })\n return\n }\n }\n\n const abortController = new AbortController()\n // Auto-abort when lease expires\n const lockMs = lease.lockExpiresAt.getTime() - Date.now()\n const lockTimer = setTimeout(\n () => {\n abortController.abort()\n },\n Math.max(0, lockMs),\n )\n\n const jobCtx: JobContext = {\n traceId,\n input: parsedInput,\n signal: abortController.signal,\n attempt: lease.attempts,\n }\n\n try {\n await def.handler(jobCtx)\n await backend.ack(lease.jobId)\n } catch (err) {\n const isNonRetryable = err instanceof NonRetryableError\n await backend.nack(lease.jobId, {\n error: err instanceof Error ? err.message : String(err),\n nonRetryable: isNonRetryable,\n })\n } finally {\n clearTimeout(lockTimer)\n }\n }\n\n return {\n async tick(opts) {\n const leases = await backend.dequeue({\n batchSize: opts?.batchSize ?? 10,\n lockSeconds: opts?.lockSeconds ?? 30,\n })\n // Process leases sequentially — caller controls concurrency via\n // batchSize and tick frequency.\n for (const lease of leases) {\n await runOne(lease)\n }\n return leases.length\n },\n }\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { JobNode } from './job-scan.js'\n\nexport const JOB_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface JobManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport interface JobManifest {\n readonly schemaVersion: typeof JOB_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly jobs: readonly JobManifestEntry[]\n}\n\nexport function buildJobManifest(nodes: readonly JobNode[], projectRoot?: string): JobManifest {\n const jobs: JobManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n maxAttempts: n.maxAttempts,\n hasInputSchema: n.hasInputSchema,\n }))\n return {\n schemaVersion: JOB_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n jobs,\n }\n}\n\nexport function writeJobManifest(\n path: string,\n input: readonly JobNode[] | JobManifest,\n projectRoot?: string,\n): void {\n const manifest: JobManifest = isManifest(input) ? input : buildJobManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is JobManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'jobs' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\n\nimport { importUserModule } from '../../config/import-user-module.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { JobDefinition } from './job-types.js'\n\nexport interface JobNode {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport class DuplicateJobNameError extends Error {\n readonly code = 'DUPLICATE_JOB_NAME'\n constructor(\n public readonly jobName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate job name \"${jobName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Job names must be unique across server/jobs/.',\n )\n this.name = 'DuplicateJobNameError'\n }\n}\n\nconst JOB_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface JobModule {\n default?: unknown\n}\n\nfunction isJobDefinition(value: unknown): value is JobDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.handler === 'function' &&\n typeof def.maxAttempts === 'number' &&\n typeof def.hasInputSchema === 'boolean'\n )\n}\n\nexport async function scanJobs(jobsDir: string): Promise<JobNode[]> {\n if (!existsSync(jobsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(jobsDir, { extensions: JOB_EXTENSIONS }, (p) => {\n const base = basename(p)\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: JobNode[] = []\n for (const filePath of filePaths) {\n let mod: JobModule\n try {\n mod = await importUserModule(filePath)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import job file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isJobDefinition(exported)) {\n throw new Error(\n `Job file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineJob(name, { handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n maxAttempts: exported.maxAttempts,\n hasInputSchema: exported.hasInputSchema,\n })\n }\n\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateJobNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n"],"mappings":"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","names":["now","r"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/load-env.ts","../src/server/transformer.ts","../src/server/http/validate-route-input.ts","../src/server/http/web-middleware-runner.ts","../src/server/web-handler.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Reads `.env` files from a CLI-controlled `cwd`. File names are a fixed\n * literal set. Build-time + CLI tool; no HTTP input.\n */\n/**\n * `loadEnv()` — auto-loads `.env` files into `process.env` for server code.\n *\n * Implements the Next.js `loadEnvConfig` algorithm\n * (`referencias/next.js/packages/next-env/index.ts:114-180`) with TheoKit\n * adaptations:\n *\n * - EC-1: 1MB file-size cap (anti-OOM, anti-supply-chain).\n * - EC-2: `_resetEnvCache()` test-only side-door.\n * - EC-8: `dotenv-expand` circular-ref safe (lib-provided).\n * - EC-13: log when `.env` is a symlink (transparency).\n * - D6: real `process.env` wins over `.env`-file values.\n * - NODE_ENV stash: `.env`-set NODE_ENV NEVER overrides the real\n * `process.env.NODE_ENV`. Stashed in `__THEOKIT_USER_NODE_ENV` instead.\n */\n\nimport { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport dotenv from 'dotenv'\nimport { expand, type DotenvPopulateInput } from 'dotenv-expand'\n\nimport type { LoadEnvOptions, LoadEnvResult } from './load-env-types.js'\n\n/** EC-1: 1MB cap. Real `.env`s are < 10KB. */\nconst MAX_ENV_FILE_BYTES = 1_048_576\n\nconst cache = new Map<string, LoadEnvResult>()\n\n/** Test-only side-door for EC-2 — clear the module-level cache between vitest test files. */\nexport function _resetEnvCache(): void {\n cache.clear()\n}\n\nfunction envFilesInPriorityOrder(mode: string): string[] {\n // Top of list = highest priority. Will be read in REVERSE so first-in\n // wins via overwrite during merge.\n const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`]\n // Test mode skips `.env.local` per dotenv convention (avoid leaking dev\n // secrets into the test suite).\n if (mode === 'test') {\n return files.filter((f) => f !== '.env.local')\n }\n return files\n}\n\nfunction readEnvFile(path: string): Record<string, string> | null {\n let stat\n try {\n stat = statSync(path)\n } catch {\n return null // ENOENT / EACCES → skip\n }\n\n // Allow files + FIFOs (1Password/SOPS pipe support).\n if (!stat.isFile() && !stat.isFIFO()) return null\n\n // EC-1 — anti-OOM cap.\n if (stat.size > MAX_ENV_FILE_BYTES) {\n console.warn(\n `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes — skipping (likely a generated artifact, not a real env file)`,\n )\n return null\n }\n\n // EC-13 — symlink transparency. Don't refuse; just log.\n try {\n const lstat = lstatSync(path)\n if (lstat.isSymbolicLink()) {\n const real = realpathSync(path)\n // eslint-disable-next-line no-console -- intentional transparency log on a build-time tool\n console.info(`[theokit] .env at ${path} is a symlink → ${real}`)\n }\n } catch {\n // lstat failure is non-fatal — fall through to read.\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return dotenv.parse(content)\n } catch {\n return null\n }\n}\n\n// eslint-disable-next-line complexity -- canonical env-load algorithm (priority → expand → guards → apply); inlining branches is clearer than extracting micro-helpers\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult {\n const cwd = options.cwd ?? process.cwd()\n const mode = options.mode ?? process.env.NODE_ENV ?? 'development'\n const cacheKey = `${cwd}:${mode}`\n\n if (!options.forceReload) {\n const cached = cache.get(cacheKey)\n if (cached) return cached\n }\n\n const fileNames = envFilesInPriorityOrder(mode)\n const filePaths = fileNames.map((f) => resolve(cwd, f))\n\n // Read in REVERSE — lower-priority files first, so higher-priority\n // (lower-index) overwrites during merge.\n const merged: Record<string, string> = {}\n const loadedFromFiles: string[] = []\n for (const path of [...filePaths].reverse()) {\n const parsed = readEnvFile(path)\n if (parsed === null) continue\n for (const [k, v] of Object.entries(parsed)) {\n merged[k] = v\n }\n loadedFromFiles.unshift(path) // keep priority order in result\n }\n\n // NODE_ENV stash — `.env`-set NODE_ENV never overrides real process.env.NODE_ENV.\n if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === undefined) {\n process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV\n }\n delete merged.NODE_ENV // never propagate\n\n // EC-8 — dotenv-expand@11 stack-overflows on circular refs (A=${B}, B=${A}).\n // Wrap in try/catch — on overflow, leave the parsed map untouched (literal\n // values survive). Tested via test_loadEnv_circular_expand_no_loop_EC8.\n // Pass a CLONE of process.env (string-only, filtered) so expand can\n // reference real env without mutating it.\n const processEnvClone: DotenvPopulateInput = {}\n for (const [k, v] of Object.entries(process.env)) {\n if (typeof v === 'string') processEnvClone[k] = v\n }\n try {\n expand({ parsed: merged, processEnv: processEnvClone })\n } catch (err) {\n if (err instanceof RangeError) {\n console.warn(\n `[theokit] .env expansion overflow (likely circular reference like A=\\${B}, B=\\${A}). Leaving values as literals.`,\n )\n } else {\n throw err\n }\n }\n\n // Apply: D6 — real process.env wins over file values.\n const loaded: Record<string, string> = {}\n for (const [k, v] of Object.entries(merged)) {\n if (process.env[k] !== undefined) continue // real env wins\n process.env[k] = v\n loaded[k] = v\n }\n\n // Sentinel\n process.env.__THEOKIT_PROCESSED_ENV = 'true'\n\n const result: LoadEnvResult = { loaded, loadedFromFiles }\n cache.set(cacheKey, result)\n return result\n}\n","import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * M33 — the SINGLE route-input validation pipeline, shared by the HTTP path (`web-handler.ts`\n * `runHandler`) and the in-process caller (`in-process-caller.ts` `callProcedure`).\n *\n * Both surfaces validate the same three channels (`query`/`body`/`params`) against the same\n * `RouteConfig` Zod schemas. Extracting the logic here means there is ONE validation pipeline\n * (no duplication, no drift) — each caller only differs in how it MAPS a failure: the HTTP path\n * emits a 400 Response, the in-process path throws a typed error.\n */\nimport type { z } from 'zod'\n\n/** The three request-input channels a route may declare a Zod schema for. */\nexport type RouteInputChannel = 'query' | 'body' | 'params'\n\n/**\n * The minimal structural shape the validator needs: the three optional channel schemas. Both\n * `RouteConfig` (the 5-arity generic contract) and `WebRouteHandlerConfig` (the Web runtime shape)\n * satisfy this — the validator depends only on the schemas, not the full route generic.\n */\nexport interface RouteInputSchemas {\n query?: z.ZodType\n body?: z.ZodType\n params?: z.ZodType\n}\n\n/** Raw (pre-validation) channel values. A channel absent from the config passes through unchanged. */\nexport interface RawRouteInput {\n query?: unknown\n body?: unknown\n params?: unknown\n}\n\n/** Discriminated result — each caller maps `ok:false` to its own surface (Response vs throw). */\nexport type RouteInputValidation =\n | { ok: true; query: unknown; body: unknown; params: unknown }\n | { ok: false; channel: RouteInputChannel; error: z.ZodError }\n\n/**\n * Validate the three input channels against the route's declared Zod schemas. A channel with no\n * declared schema passes its raw value through. First failing channel short-circuits (query → body\n * → params order, matching `runHandler`).\n */\nexport function validateRouteInput(\n config: RouteInputSchemas,\n raw: RawRouteInput,\n): RouteInputValidation {\n let query: unknown = raw.query\n if (config.query !== undefined) {\n const parsed = config.query.safeParse(raw.query)\n if (!parsed.success) return { ok: false, channel: 'query', error: parsed.error }\n query = parsed.data\n }\n let body: unknown = raw.body\n if (config.body !== undefined) {\n const parsed = config.body.safeParse(raw.body)\n if (!parsed.success) return { ok: false, channel: 'body', error: parsed.error }\n body = parsed.data\n }\n let params: unknown = raw.params\n if (config.params !== undefined) {\n const parsed = config.params.safeParse(raw.params)\n if (!parsed.success) return { ok: false, channel: 'params', error: parsed.error }\n params = parsed.data\n }\n return { ok: true, query, body, params }\n}\n","/**\n * T3.2 — Web-Standards middleware runner for the `executeWebRequest` path.\n *\n * The Node request path (`http/execute.ts`) runs middleware via\n * `runMiddlewareAndContext`, which is coupled to Node `req`/`res`. Per G8\n * (Web Standards over Node APIs) the Web path needs a `Request`-shaped runner;\n * this is it. Extracted to a sibling so `web-handler.ts` stays under its LoC\n * budget (G6).\n *\n * Ordering invariant (EC-3): the caller runs the CSRF gate BEFORE invoking this\n * runner, mirroring the Node path (CSRF stage precedes user middleware).\n *\n * NOTE (D4 / EC-12): this is its own middleware contract — `(request, context)`.\n * Full semantic parity with the Node `defineMiddleware` contract is part of the\n * deferred Node→Web pipeline convergence, not this slice.\n */\n\n/** A Web-path middleware: mutate `context`, or return a `Response` to short-circuit. */\nexport type WebMiddleware = (\n request: Request,\n context: Record<string, unknown>,\n // A middleware may mutate `context` and return nothing (void) OR return a\n // `Response` to short-circuit (Express/Koa-style). `void` in this union is\n // intentional — `runWebMiddleware` only inspects `instanceof Response`, so a\n // void return means \"continue to the next middleware / handler\".\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n) => Promise<Response | undefined> | Response | undefined | void\n\n/**\n * Run middleware in order against a shared mutable `context`. The first\n * middleware that returns a `Response` short-circuits the chain (the handler\n * is not reached); its `Response` is returned verbatim so headers like\n * `Set-Cookie` are preserved (EC-11). Returns `undefined` when every\n * middleware passes (the handler should run next).\n */\nexport async function runWebMiddleware(\n request: Request,\n middleware: readonly WebMiddleware[],\n context: Record<string, unknown>,\n): Promise<Response | undefined> {\n for (const mw of middleware) {\n const result = await mw(request, context)\n if (result instanceof Response) return result\n }\n return undefined\n}\n","/**\n * T5a.2 Phase A — Foundation: Web-Standards request handler entry-point.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase A. Bridges the gap between Web `Request`/`Response` and the\n * existing `defineRoute` config shape (which is consumed today by the Node-\n * shaped `executeRoute`).\n *\n * This is the entry-point T1.2 RED tests expect:\n * `executeWebRequest(request: Request, routeModule: { GET?, POST?, ... }): Promise<Response>`\n *\n * Architecture per ADR-0028 (R3a) — this function accepts a native Web\n * `Request` and returns a native Web `Response`. No `node:*` is required in\n * the implementation (Zod + native Headers/URL/Response cover the surface).\n *\n * **Scope (intentionally narrow):**\n * - Method dispatch (GET/POST/PUT/PATCH/DELETE).\n * - Zod validation for `query` (from URL.searchParams), `body` (from\n * request.json() OR request.text()), `params` (consumer-supplied OR\n * parsed from path).\n * - Handler invocation.\n * - Result → JSON Response (200 default, 204 for void return).\n * - Error → envelope-shaped JSON Response (400 for validation, 500 for\n * handler throws) per G5 boundary translation (serverErrorToEnvelope).\n *\n * **NOT included (deferred to T5a.2 Phase B-G):**\n * - Plugin runner integration (onRequest/preHandler/onResponse hooks).\n * - CSRF / CORS / security headers / rate limiting / cookies / auth.\n * - Middleware chain. SSR rendering. WebSocket upgrade. File upload.\n * - File-system routing scan; consumer provides the route module\n * explicitly (Web Request entry doesn't yet know about scan results).\n *\n * The narrow scope makes this a viable Phase A landing zone — turns the\n * 7 T1.2 RED tests GREEN without prematurely refactoring the full\n * IncomingMessage→Request boundary across server/http/ files.\n */\nimport type { z } from 'zod'\n\nimport { envelopeCodeToStatus } from '../core/contracts/envelope-code-to-status.js'\nimport type { TheoErrorEnvelope } from '../core/contracts/error-envelope.js'\nimport { serverErrorToEnvelope } from '../core/contracts/server-error-to-envelope.js'\nimport { TheoError } from '../core/contracts/theo-error.js'\n\nimport { isZodLike } from './http/execute-stages.js'\nimport { validateRouteInput } from './http/validate-route-input.js'\nimport { runWebMiddleware, type WebMiddleware } from './http/web-middleware-runner.js'\nimport type {\n WebOnErrorHook,\n WebOnRequestHook,\n WebOnResponseHook,\n WebPluginContext,\n WebPluginErrorContext,\n WebPreHandlerHook,\n} from './plugin-types.js'\nimport { validateCsrfRequest } from './security/csrf.js'\n\nexport type { WebMiddleware }\n\n/**\n * Minimal structural type for a `defineRoute` config. Mirrors\n * `core/contracts/route-config.ts` shape but only the fields this entry\n * point needs at runtime (Zod schemas + handler). Kept structural to\n * avoid a hard dep on the full RouteConfig generic chain.\n */\ninterface WebRouteHandlerConfig {\n query?: z.ZodType\n body?: z.ZodType\n params?: z.ZodType\n /** Optional Zod schema validating a plain-object handler return (D1/D2). */\n response?: z.ZodType\n /** Honored for plain-object returns to match the Node runner (D3). */\n status?: number\n handler: (ctx: {\n query: unknown\n body: unknown\n params: unknown\n request: Request\n /** Mutable per-request context populated by the Web middleware chain (T3.2). */\n context: Record<string, unknown>\n }) => unknown\n}\n\n/**\n * The route module exported by a `server/routes/*.ts` file (after T2.6 G6\n * router lockdown). Method-named exports map to `defineRoute` results.\n */\ntype WebRouteModule = Partial<Record<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', unknown>>\n\n/**\n * Coerce URL.searchParams into a plain `{ key: value }` object so Zod\n * `z.object({...})` schemas work without per-route adapters. Repeated keys\n * become arrays (most browsers serialize multi-select that way).\n */\nfunction searchParamsToObject(params: URLSearchParams): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {}\n for (const [key, value] of params.entries()) {\n if (!Object.hasOwn(out, key)) {\n out[key] = value\n continue\n }\n const existing = out[key]\n if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n out[key] = [existing, value]\n }\n }\n return out\n}\n\n/**\n * Parse request body based on Content-Type. JSON is the canonical path;\n * empty body returns `undefined` (the handler treats as \"no body\").\n *\n * **Inline-only mode (default):** handles `application/json` and `text/*`.\n * Other content-types return `undefined`. Multipart/form-data requires\n * the opt-in `bodyParser: 'full'` mode (T5a.2 Phase E).\n */\nasync function parseBodyInline(request: Request): Promise<unknown> {\n // GET/HEAD have no body by spec; even if the consumer sets one, ignore it\n // (matches Web Request semantics — `body` is null for GET/HEAD anyway).\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const contentType = request.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const text = await request.text()\n if (text.length === 0) return undefined\n try {\n return JSON.parse(text)\n } catch {\n // Malformed JSON — return undefined; Zod schema (if any) will fail with\n // a more useful \"required\" message than a raw SyntaxError.\n return undefined\n }\n }\n // text/plain or no body: return raw text or undefined\n if (contentType.startsWith('text/')) {\n const text = await request.text()\n return text.length === 0 ? undefined : text\n }\n return undefined\n}\n\n/**\n * T5a.2 Phase E — \"full\" body parser delegating to `parseWebRequestBody`.\n * Returns a `ParsedWebBody` (`{ json?, fields, files }`) for JSON OR\n * multipart, or `undefined` when the body is empty / unparseable.\n *\n * Handler downstream pattern:\n * `body?.json` (JSON requests)\n * `body?.fields` + `body?.files` (multipart upload requests)\n *\n * Limits enforced by parseWebRequestBody:\n * - declared Content-Length cap (default 10MB × maxFiles + 1MB margin)\n * - per-file size cap (default 10MB)\n * - max files (default 10)\n */\nasync function parseBodyFull(request: Request): Promise<unknown> {\n if (request.method === 'GET' || request.method === 'HEAD') return undefined\n const { parseWebRequestBody } = await import('./body-parser-web.js')\n try {\n const parsed = await parseWebRequestBody(request)\n // If nothing parsed (empty body OR unhandled content-type), return undefined\n // so Zod schemas treat it as missing — same semantics as inline parser.\n if (\n parsed.json === undefined &&\n Object.keys(parsed.fields).length === 0 &&\n parsed.files.length === 0\n ) {\n return undefined\n }\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Build an envelope-shaped 400 Response for a Zod validation failure. Uses\n * the canonical `TheoErrorEnvelope` shape per G5 D3 (boundary translation).\n */\nfunction validationErrorResponse(zodError: z.ZodError, field: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'BAD_REQUEST',\n message: `Validation failed: ${field}`,\n ext: {\n fields: zodError.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n })),\n },\n }\n return new Response(JSON.stringify(envelope), {\n status: 400,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Run a `defineRoute` config against a parsed Web Request. Returns the raw\n * handler result; serialization to Response happens in the caller.\n *\n * Throws if Zod validation fails (caller catches and emits 400).\n */\nasync function runHandler(\n config: WebRouteHandlerConfig,\n request: Request,\n bodyParser: 'inline' | 'full' = 'inline',\n paramsInput: Record<string, string> = {},\n context: Record<string, unknown> = {},\n): Promise<{ ok: true; result: unknown } | { ok: false; response: Response }> {\n const url = new URL(request.url)\n const queryRaw = searchParamsToObject(url.searchParams)\n const bodyRaw =\n bodyParser === 'full' ? await parseBodyFull(request) : await parseBodyInline(request)\n // T3.1 — route params resolved upstream (matchRoute) and threaded via\n // opts.params. Defaults to `{}` so callers that don't supply params keep the\n // prior behavior (a route declaring config.params then fails validation).\n const paramsRaw = paramsInput\n\n // M33 — validate the three input channels via the SINGLE shared pipeline (`validateRouteInput`),\n // the same one the in-process caller (`callProcedure`) uses. The HTTP path maps a failure to a\n // 400 Response; the in-process path throws. One pipeline, no drift.\n const validated = validateRouteInput(config, {\n query: queryRaw,\n body: bodyRaw,\n params: paramsRaw,\n })\n if (!validated.ok) {\n return { ok: false, response: validationErrorResponse(validated.error, validated.channel) }\n }\n const { query, body, params } = validated\n\n const result = await config.handler({ query, body, params, request, context })\n return validateResponseOutput(config.response, result) ?? { ok: true, result }\n}\n\n/**\n * Validate a plain-object handler return against `config.response` (D1/D2). A\n * mismatch is a SERVER fault → 500 envelope. `Response`-instance returns and\n * `undefined`/`null` (→ 204) skip validation — parity with the Node runner,\n * which validates only in its plain-object branch. Returns `undefined` when no\n * validation applies (caller forwards the raw result).\n */\nfunction validateResponseOutput(\n response: unknown,\n result: unknown,\n): { ok: true; result: unknown } | { ok: false; response: Response } | undefined {\n const validatable = result !== undefined && result !== null && !(result instanceof Response)\n if (!validatable || !isZodLike(response)) return undefined\n const parsed = response.safeParse(result)\n if (parsed.success) return { ok: true, result: parsed.data }\n const err = new TheoError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'response validation failed',\n ext: { issues: parsed.error?.issues },\n })\n return { ok: false, response: handlerErrorResponse(err) }\n}\n\n/**\n * Serialize a handler return value into a native Web `Response`. Conventions:\n * - `undefined` / `void` → 204 No Content.\n * - existing `Response` instance → pass through unchanged.\n * - everything else → 200 JSON.\n */\nfunction toResponse(result: unknown, status?: number): Response {\n if (result === undefined) {\n return new Response(null, { status: 204 })\n }\n if (result instanceof Response) {\n return result\n }\n return new Response(JSON.stringify(result), {\n status: status ?? 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Build an envelope-shaped 500 Response when a handler throws. Uses\n * `serverErrorToEnvelope` so existing ad-hoc Error classes (AuthRequired,\n * FileTooLarge, etc.) get mapped to canonical envelope codes.\n */\nfunction handlerErrorResponse(err: unknown): Response {\n const envelope = serverErrorToEnvelope(err)\n // Map envelope code → HTTP status when possible. Default to 500 for\n // INTERNAL_SERVER_ERROR. Other codes map per HTTP semantics.\n const status = envelopeCodeToStatus(envelope.code)\n return new Response(JSON.stringify(envelope), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n// envelopeCodeToStatus extracted to core/contracts/envelope-code-to-status.ts\n// (architecture-remediation T1.2, 2026-06-12)\n\n/**\n * Optional behavior knobs for `executeWebRequest`. Each knob defaults to\n * the safe \"no-op\" stance so existing Phase A consumers (T1.2 fixture\n * tests) keep working unchanged.\n *\n * **`csrfMode`** (T5a.2 Phase B slice 1/6) — when set to `'strict'`, the\n * Web-Standards request gate runs `validateCsrfRequest` BEFORE method\n * dispatch on state-changing methods (POST/PUT/PATCH/DELETE) and emits a\n * `403 FORBIDDEN` envelope when the check fails. Default: `'off'` (no\n * CSRF enforcement) to preserve Phase A backward compat. Production\n * consumers SHOULD pass `csrfMode: 'strict'`.\n *\n * Per the T5a.2 plan v1.0 § Phase B header-only leaves: csrf.ts is the\n * first leaf to be migrated (slice 1/6); 5 more sibling leaves remain\n * (csrf-multi-header, csrf-readiness-endpoint, csp-report, cors, cookies).\n */\nexport interface ExecuteWebRequestOptions {\n csrfMode?: 'off' | 'strict'\n /**\n * T3.1 — route params resolved upstream by `matchRoute` (e.g. `{ id: '42' }`\n * for `/users/:id`). Threaded to the handler's `params` input and validated\n * against `config.params` (Zod) when declared. Defaults to `{}` — callers\n * that don't supply params keep the prior behavior (additive, backward-compat).\n */\n params?: Record<string, string>\n /**\n * T3.2 — Web-Standards middleware chain. Runs in order AFTER the CSRF gate\n * (EC-3) and BEFORE the handler. A middleware returning a `Response`\n * short-circuits (handler not reached); mutating `context` passes data to\n * the handler. Omitted → zero overhead (handler runs directly).\n */\n middleware?: readonly WebMiddleware[]\n /**\n * T5a.2 Phase E — body parser strategy.\n *\n * - `'inline'` (default): handle `application/json` + `text/*` only.\n * Returns the parsed value (object for JSON, string for text). Other\n * content-types (e.g., multipart) return `undefined`.\n * - `'full'`: delegate to `parseWebRequestBody` for multipart support\n * + per-file size caps + max-files cap (Web Standards `request.formData()`\n * under the hood). Returns a `ParsedWebBody` object:\n * `{ json?, fields, files }`. Multipart consumers MUST opt into this\n * mode; JSON-only routes pay zero cost staying on `'inline'`.\n */\n bodyParser?: 'inline' | 'full'\n /**\n * T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n *\n * Adapters (Node, CF Workers, Bun, Deno) wire `WebPluginContext`-shaped\n * hooks at the executeWebRequest lifecycle. Lifecycle order mirrors the\n * Fastify / Hono convention:\n *\n * 1. CSRF check (when opts.csrfMode === 'strict')\n * 2. onRequest — earliest, before body parsing. Plugins can short-circuit\n * by setting `ctx.response` (handler skipped; subsequent hooks see\n * the short-circuit response).\n * 3. body parse (inline OR full per opts.bodyParser)\n * 4. preHandler — after body parsed, before handler runs. Same\n * short-circuit semantic.\n * 5. handler invocation\n * 6. onResponse — after handler returns OR after a hook short-circuit.\n * `ctx.response` is populated.\n * 7. onError — fires if any of (handler, onRequest, preHandler) throws.\n * `ctx.response` is the envelope-shaped error response built via\n * serverErrorToEnvelope.\n *\n * `responseHeaders` is shared across hooks; the final Response merges\n * them with the handler's Response headers (handler headers win on\n * conflict; hook headers add new ones). Decorations made via\n * `ctx.ctx[key] = value` persist across hooks (request-scoped state).\n *\n * `hooks: undefined` (default) → no plugin lifecycle, Phase A behavior\n * preserved. Production consumers wire via the WebPluginRunner facade\n * (a future Phase G slice).\n */\n hooks?: {\n onRequest?: readonly WebOnRequestHook[]\n preHandler?: readonly WebPreHandlerHook[]\n onResponse?: readonly WebOnResponseHook[]\n onError?: readonly WebOnErrorHook[]\n }\n /**\n * Stable request identifier propagated into hook contexts. Adapters\n * resolve via traceparent / x-request-id / generated UUID (see\n * `extractTraceIdFromRequest` from Phase C slice 1/2). Default:\n * `globalThis.crypto.randomUUID()`.\n */\n requestId?: string\n}\n\n/**\n * Methods that require CSRF enforcement when `csrfMode: 'strict'`. GET\n * and HEAD are read-only per HTTP semantics and bypass CSRF (the threat\n * model is state-changing requests forged via cross-origin POSTs); OPTIONS\n * is the CORS preflight and also bypasses.\n */\nexport const CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/**\n * Web-Standards entry-point for executing a route module against a native\n * Web `Request`. Returns a native Web `Response`.\n *\n * Method dispatch: looks up the handler keyed by `request.method`\n * (uppercase). Returns 405 if the method isn't exported.\n *\n * @example\n * import * as users from './app/users/route.js'\n * import { executeWebRequest } from 'theokit/server'\n *\n * const response = await executeWebRequest(\n * new Request('http://localhost/api/users', { method: 'GET' }),\n * users,\n * { csrfMode: 'strict' },\n * )\n */\n/**\n * T5a.2 Phase G slice 1/N — merge hook-mutated response headers into the\n * handler's Response. Handler-set headers win on conflict (the handler\n * has the most context about its own response); hook headers add new\n * entries (e.g., CORS, Set-Cookie). Returns a new Response with the\n * merged headers + same body/status as the source.\n *\n * Set-Cookie is multi-value at the Web spec layer — `getSetCookie()` is\n * the only way to retrieve multiple values. The merge appends ALL hook-\n * set Set-Cookie values to the response (multiple Set-Cookie headers is\n * spec-correct and how browsers expect cookie issuance).\n */\nfunction mergeHookHeaders(response: Response, hookHeaders: Headers): Response {\n if ([...hookHeaders].length === 0) return response\n const merged = new Headers(response.headers)\n for (const [k, v] of hookHeaders.entries()) {\n if (k.toLowerCase() === 'set-cookie') continue // handled separately\n if (!merged.has(k)) merged.set(k, v)\n }\n for (const sc of hookHeaders.getSetCookie()) {\n merged.append('Set-Cookie', sc)\n }\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n })\n}\n\n/** Build a 405 envelope Response. */\nfunction methodNotAllowedResponse(method: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'METHOD_NOT_ALLOWED',\n message: `Method ${method} not allowed`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 405,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/** Build a 403 CSRF envelope Response. */\nfunction csrfFailedResponse(reason: string): Response {\n const envelope: TheoErrorEnvelope = {\n code: 'FORBIDDEN',\n message: `CSRF check failed: ${reason}`,\n }\n return new Response(JSON.stringify(envelope), {\n status: 403,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport async function executeWebRequest(\n request: Request,\n routeModule: WebRouteModule,\n opts: ExecuteWebRequestOptions = {},\n): Promise<Response> {\n const method = request.method.toUpperCase() as keyof WebRouteModule\n const config = routeModule[method] as WebRouteHandlerConfig | undefined\n\n // T5a.2 Phase G slice 1/N — plugin lifecycle hooks.\n // When opts.hooks is undefined, skip the entire hook orchestration to\n // preserve Phase A backward compat (zero overhead for consumers not\n // wiring hooks). In the no-hooks branch, dispatch + CSRF gates run\n // FIRST and 405 short-circuits before the handler runs.\n if (opts.hooks === undefined) {\n if (config === undefined || typeof config.handler !== 'function') {\n return methodNotAllowedResponse(method)\n }\n if (opts.csrfMode === 'strict' && CSRF_PROTECTED_METHODS.has(method)) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) return csrfFailedResponse(csrfCheck.reason)\n }\n try {\n // T3.2 — middleware runs AFTER the CSRF gate (EC-3), BEFORE the handler.\n const context: Record<string, unknown> = {}\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, context)\n if (shortCircuit) return shortCircuit\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n context,\n )\n if (!outcome.ok) return outcome.response\n return toResponse(outcome.result, config.status)\n } catch (err) {\n return handlerErrorResponse(err)\n }\n }\n\n // With hooks: onRequest hooks run BEFORE method dispatch (Hono /\n // Fastify convention) so CORS preflight plugins can intercept OPTIONS\n // requests regardless of route shape. If no hook short-circuits, then\n // 405 fires when the method isn't exported by the route module.\n return runWithHooks(request, config, opts, opts.hooks)\n}\n\n/**\n * Run the request through the full plugin lifecycle. Extracted to keep\n * `executeWebRequest`'s cyclomatic + cognitive complexity under the lint\n * caps (15/20). Pure side-effect surface — same return type as the\n * no-hooks branch.\n */\n/**\n * Pre-handler pipeline: onRequest hooks → CSRF gate → preHandler hooks →\n * method dispatch + handler. Mutates `hookCtx.response` on short-circuit\n * OR on handler completion. Extracted from `runWithHooks` to keep the\n * latter's cyclomatic complexity under the lint cap (15).\n */\nasync function runPreHandlerPipeline(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<void> {\n const method = request.method.toUpperCase()\n const runList = async (list: readonly WebOnRequestHook[]): Promise<void> => {\n for (const hook of list) {\n if (hookCtx.response !== undefined) return\n await hook(hookCtx)\n }\n }\n // onRequest first (CORS preflight + auth gate intercept before dispatch).\n if (hooks.onRequest) await runList(hooks.onRequest)\n // CSRF gate AFTER onRequest (auth-short-circuit avoids CSRF cost) but\n // BEFORE the handler.\n if (\n hookCtx.response === undefined &&\n opts.csrfMode === 'strict' &&\n CSRF_PROTECTED_METHODS.has(method)\n ) {\n const csrfCheck = validateCsrfRequest(request)\n if (!csrfCheck.valid) hookCtx.response = csrfFailedResponse(csrfCheck.reason)\n }\n if (hookCtx.response === undefined && hooks.preHandler) {\n await runList(hooks.preHandler)\n }\n if (hookCtx.response === undefined) {\n await runHandlerStage(hookCtx, request, config, opts, method)\n }\n}\n\n/**\n * T3.2 — the middleware + handler stage of the hook pipeline. Extracted from\n * `runPreHandlerPipeline` to keep its cyclomatic complexity under the lint cap.\n * Order: 405 check → user middleware (after CSRF + preHandler) → handler.\n */\nasync function runHandlerStage(\n hookCtx: WebPluginContext,\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n method: string,\n): Promise<void> {\n // 405 if route module doesn't export this method.\n if (config === undefined || typeof config.handler !== 'function') {\n hookCtx.response = methodNotAllowedResponse(method)\n return\n }\n // hookCtx.ctx is the shared per-request context the middleware mutates.\n if (opts.middleware?.length) {\n const shortCircuit = await runWebMiddleware(request, opts.middleware, hookCtx.ctx)\n if (shortCircuit) {\n hookCtx.response = shortCircuit\n return\n }\n }\n const outcome = await runHandler(\n config,\n request,\n opts.bodyParser ?? 'inline',\n opts.params ?? {},\n hookCtx.ctx,\n )\n hookCtx.response = outcome.ok ? toResponse(outcome.result, config.status) : outcome.response\n}\n\nasync function runWithHooks(\n request: Request,\n config: WebRouteHandlerConfig | undefined,\n opts: ExecuteWebRequestOptions,\n hooks: NonNullable<ExecuteWebRequestOptions['hooks']>,\n): Promise<Response> {\n const hookCtx: WebPluginContext = {\n request,\n responseHeaders: new Headers(),\n ctx: {},\n requestId: opts.requestId ?? globalThis.crypto.randomUUID(),\n }\n try {\n await runPreHandlerPipeline(hookCtx, request, config, opts, hooks)\n if (hooks.onResponse) {\n for (const hook of hooks.onResponse) {\n await hook(hookCtx)\n }\n }\n // runPreHandlerPipeline guarantees hookCtx.response is set (via either\n // a hook short-circuit OR handler outcome OR 405). TS doesn't infer this\n // post-mutation; the assertion is safe per the function's contract.\n const finalResponse =\n hookCtx.response ??\n new Response(\n JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'No response built' }),\n {\n status: 500,\n headers: { 'content-type': 'application/json' },\n },\n )\n return mergeHookHeaders(finalResponse, hookCtx.responseHeaders)\n } catch (err) {\n return runErrorHooks(err, hookCtx, hooks.onError)\n }\n}\n\n/**\n * Run onError hooks against an envelope-shaped error response. EC-9 —\n * each hook throw is swallowed to avoid error-in-error-handler recursion.\n * Returns the merged final Response.\n */\nasync function runErrorHooks(\n err: unknown,\n hookCtx: WebPluginContext,\n onError: readonly WebOnErrorHook[] | undefined,\n): Promise<Response> {\n const errorResponse = handlerErrorResponse(err)\n if (onError !== undefined) {\n const errorCtx: WebPluginErrorContext = {\n ...hookCtx,\n response: errorResponse,\n error: err,\n }\n for (const hook of onError) {\n try {\n await hook(errorCtx)\n } catch {\n // EC-9: error in error handler — swallow to avoid recursion.\n }\n }\n }\n return mergeHookHeaders(errorResponse, hookCtx.responseHeaders)\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/jobs/duplicate-context-key-error.ts","../src/server/jobs/outbox.ts","../src/server/jobs/queue-client.ts"],"sourcesContent":["/**\n * Thrown when a plugin or middleware decorates `ctx.<key>` with a value\n * that the framework would also like to inject (e.g., `ctx.queue` from\n * the jobs backend wiring).\n *\n * Per EC-202 of system-100-percent-functional-plan: silent override is a\n * latent bug class. Fail loud so the conflict surfaces immediately.\n */\nexport class DuplicateContextKeyError extends Error {\n readonly code = 'DUPLICATE_CONTEXT_KEY'\n constructor(\n public readonly key: string,\n opts?: { reason?: string },\n ) {\n super(\n `Duplicate context key \"${key}\". ${opts?.reason ?? 'The framework cannot inject a value for a key already decorated by middleware or a plugin.'}`,\n )\n this.name = 'DuplicateContextKeyError'\n }\n}\n","import type { JobEnqueueInput } from './job-backend.js'\n\n/**\n * Transactional outbox buffer for `ctx.queue.enqueue` (T2.5).\n *\n * Lifecycle (wired in `http/execute.ts` by T2.5 integration step):\n * 1. Request handler invoked → create per-request outbox.\n * 2. Handler calls `ctx.queue.enqueue(...)` → outbox.push(entry).\n * 3a. Response committed (`res.on('finish')` + statusCode < 400) →\n * outbox.flush(backend.enqueue).\n * 3b. Response errors (statusCode >= 400, handler throws, `res.on('close')`\n * without finish) → outbox.discard().\n *\n * Invariants:\n * - Outbox NEVER dispatches before commit. Backend.enqueue is NEVER\n * called on the request hot path.\n * - Handler throws → ZERO jobs dispatched. KEY guarantee from ADR-0003.\n *\n * EC-107: when `backend.enqueue` throws DURING flush (after response\n * committed), we log + continue. The response is already gone; partial\n * dispatch is better than zero. Each failure goes to `onError` (default:\n * `console.warn`).\n */\n\nexport interface OutboxFlushOptions {\n /**\n * Called once per failed entry. Default: `console.warn` with the\n * entry name (NOT input — privacy). Throw nothing back to caller —\n * flush always completes.\n */\n onError?: (entryName: string, errorMessage: string) => void\n}\n\nexport interface Outbox {\n push(entry: JobEnqueueInput): void\n drain(): JobEnqueueInput[]\n discard(): void\n size(): number\n /**\n * Dispatch all buffered entries via `dispatcher`. Returns after all\n * entries attempted. Per-entry failures invoke `opts.onError` (or\n * default warn) and do NOT abort the loop.\n */\n flush(\n dispatcher: (entry: JobEnqueueInput) => Promise<unknown>,\n opts?: OutboxFlushOptions,\n ): Promise<void>\n}\n\nexport function createOutbox(): Outbox {\n let buffer: JobEnqueueInput[] = []\n\n return {\n push(entry) {\n buffer.push(entry)\n },\n drain() {\n const out = buffer\n buffer = []\n return out\n },\n discard() {\n buffer = []\n },\n size() {\n return buffer.length\n },\n async flush(dispatcher, opts) {\n const entries = buffer\n buffer = []\n const onError =\n opts?.onError ??\n ((name, msg) => {\n console.warn(`[theokit:jobs:outbox] flush error for \"${name}\": ${msg}`)\n })\n for (const e of entries) {\n try {\n await dispatcher(e)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n onError(e.name, message)\n }\n }\n },\n }\n}\n","import type { JobBackend, JobEnqueueInput } from './job-backend.js'\nimport type { JobRegistry } from './job-types.js'\nimport type { Outbox } from './outbox.js'\n\n/**\n * Options forwarded to `backend.enqueue` at outbox flush time.\n */\nexport interface EnqueueOptions {\n /** Optional idempotency key for at-most-once dispatch within TTL. */\n idempotencyKey?: string\n /** Optional delay before the job becomes available. */\n delaySeconds?: number\n}\n\n/**\n * Typed queue client. Per ADR-0003, `enqueue` returns `void` and buffers\n * to the per-request outbox. `enqueueWithId` is the log-correlation\n * variant that resolves to `{ jobId }` AFTER the outbox flushes.\n *\n * Type inference: `JobName extends keyof JobRegistry`. Users extend the\n * `JobRegistry` interface via module augmentation (see `job-types.ts`).\n * Without augmentation, all enqueue calls compile-error with \"Type X is\n * not assignable to type never\" — documented in EC-110 as a known onboarding\n * friction.\n */\nexport interface QueueClient {\n /** Buffer a job to the outbox. Returns void (fire-and-forget). */\n enqueue<JobName extends keyof JobRegistry>(\n name: JobName,\n input: JobRegistry[JobName],\n opts?: EnqueueOptions,\n ): void\n /**\n * Buffer a job AND return a Promise that resolves with the jobId\n * AFTER the outbox flushes (i.e., after the response commits). NOT a\n * handle to await the job result — there is no result API.\n */\n enqueueWithId<JobName extends keyof JobRegistry>(\n name: JobName,\n input: JobRegistry[JobName],\n opts?: EnqueueOptions,\n ): Promise<{ jobId: string }>\n}\n\nexport interface CreateQueueClientOptions {\n /** W3C traceparent to propagate to enqueued jobs. */\n traceparent?: string\n}\n\n/**\n * Create a per-request queue client wired to a backend + outbox.\n *\n * The backend is referenced only to resolve `enqueueWithId` jobIds at\n * flush time — `enqueue` itself does NOT call backend on the hot path\n * (transactional outbox guarantee per ADR-0003).\n */\nexport function createQueueClient(\n backend: JobBackend,\n outbox: Outbox,\n opts: CreateQueueClientOptions = {},\n): QueueClient {\n const traceparent = opts.traceparent\n\n return {\n enqueue(name, input, enqueueOpts) {\n const entry: JobEnqueueInput = {\n name: name,\n input,\n idempotencyKey: enqueueOpts?.idempotencyKey,\n delaySeconds: enqueueOpts?.delaySeconds,\n traceparent,\n }\n outbox.push(entry)\n },\n\n enqueueWithId(name, input, enqueueOpts) {\n let resolveJobId!: (v: { jobId: string }) => void\n let rejectJobId!: (e: unknown) => void\n const promise = new Promise<{ jobId: string }>((resolve, reject) => {\n resolveJobId = resolve\n rejectJobId = reject\n })\n // Prevent unhandled rejection if the response is discarded\n void promise.catch(() => {\n /* swallow — jobId promise is best-effort log correlation */\n })\n\n // Wrap the entry so the outbox flush resolves the promise.\n const entry: JobEnqueueInput & {\n readonly __resolveJobId?: (v: { jobId: string }) => void\n readonly __rejectJobId?: (e: unknown) => void\n } = {\n name: name,\n input,\n idempotencyKey: enqueueOpts?.idempotencyKey,\n delaySeconds: enqueueOpts?.delaySeconds,\n traceparent,\n __resolveJobId: resolveJobId,\n __rejectJobId: rejectJobId,\n }\n outbox.push(entry)\n return promise\n },\n }\n}\n\n/**\n * Outbox dispatcher that bridges enqueueWithId's hidden promise channel\n * (`__resolveJobId` / `__rejectJobId`) to the backend.enqueue result.\n *\n * Use this when the request lifecycle hooks call `outbox.flush(...)`.\n */\nexport function createOutboxDispatcher(\n backend: JobBackend,\n): (entry: JobEnqueueInput) => Promise<unknown> {\n return async (entry) => {\n const augmented = entry as JobEnqueueInput & {\n __resolveJobId?: (v: { jobId: string }) => void\n __rejectJobId?: (e: unknown) => void\n }\n try {\n const result = await backend.enqueue(entry)\n augmented.__resolveJobId?.(result)\n return result\n } catch (err) {\n augmented.__rejectJobId?.(err)\n throw err\n }\n }\n}\n"],"mappings":";AAQO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAElD,YACkB,KAChB,MACA;AACA;AAAA,MACE,0BAA0B,GAAG,MAAM,MAAM,UAAU,4FAA4F;AAAA,IACjJ;AALgB;AAMhB,SAAK,OAAO;AAAA,EACd;AAAA,EAPkB;AAAA,EAFT,OAAO;AAUlB;;;AC8BO,SAAS,eAAuB;AACrC,MAAI,SAA4B,CAAC;AAEjC,SAAO;AAAA,IACL,KAAK,OAAO;AACV,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,IACA,QAAQ;AACN,YAAM,MAAM;AACZ,eAAS,CAAC;AACV,aAAO;AAAA,IACT;AAAA,IACA,UAAU;AACR,eAAS,CAAC;AAAA,IACZ;AAAA,IACA,OAAO;AACL,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,MAAM,MAAM,YAAY,MAAM;AAC5B,YAAM,UAAU;AAChB,eAAS,CAAC;AACV,YAAM,UACJ,MAAM,YACL,CAAC,MAAM,QAAQ;AACd,gBAAQ,KAAK,0CAA0C,IAAI,MAAM,GAAG,EAAE;AAAA,MACxE;AACF,iBAAW,KAAK,SAAS;AACvB,YAAI;AACF,gBAAM,WAAW,CAAC;AAAA,QACpB,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,kBAAQ,EAAE,MAAM,OAAO;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC7BO,SAAS,kBACd,SACA,QACA,OAAiC,CAAC,GACrB;AACb,QAAM,cAAc,KAAK;AAEzB,SAAO;AAAA,IACL,QAAQ,MAAM,OAAO,aAAa;AAChC,YAAM,QAAyB;AAAA,QAC7B;AAAA,QACA;AAAA,QACA,gBAAgB,aAAa;AAAA,QAC7B,cAAc,aAAa;AAAA,QAC3B;AAAA,MACF;AACA,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,IAEA,cAAc,MAAM,OAAO,aAAa;AACtC,UAAI;AACJ,UAAI;AACJ,YAAM,UAAU,IAAI,QAA2B,CAAC,SAAS,WAAW;AAClE,uBAAe;AACf,sBAAc;AAAA,MAChB,CAAC;AAED,WAAK,QAAQ,MAAM,MAAM;AAAA,MAEzB,CAAC;AAGD,YAAM,QAGF;AAAA,QACF;AAAA,QACA;AAAA,QACA,gBAAgB,aAAa;AAAA,QAC7B,cAAc,aAAa;AAAA,QAC3B;AAAA,QACA,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AACA,aAAO,KAAK,KAAK;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAQO,SAAS,uBACd,SAC8C;AAC9C,SAAO,OAAO,UAAU;AACtB,UAAM,YAAY;AAIlB,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,QAAQ,KAAK;AAC1C,gBAAU,iBAAiB,MAAM;AACjC,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,gBAAU,gBAAgB,GAAG;AAC7B,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/agent/mcp-app-resources.ts","../src/server/agent/mcp-handler.ts"],"sourcesContent":["/**\n * M30 (ADR-0041) — MCP Apps: `ui://` HTML resources for the MCP server (M16).\n *\n * A tool can declare a `ui://` HTML resource; the MCP server advertises it via `resources/list` and\n * serves the HTML via `resources/read`. The client renders it in a SANDBOXED iframe (see\n * `mcp-app-host.ts`). Pure data transforms here — no LLM, no runtime (ADR-0040 § D2 home concern).\n *\n * Security: only the `ui://` scheme is accepted (an app UI resource), never `http(s)://` — the HTML\n * is rendered sandboxed on the client, and the scheme gate keeps a tool from smuggling a remote URL\n * into the app surface.\n */\n\n/** A declared `ui://` app resource. */\nexport interface AppResource {\n uri: string\n name: string\n mimeType: 'text/html'\n html: string\n description?: string\n}\n\n/** Input to {@link defineAppResource}. */\nexport interface AppResourceInput {\n /** MUST start with `ui://`. */\n uri: string\n name: string\n html: string\n description?: string\n}\n\n/** An MCP `resources/list` descriptor (no HTML body — that comes from `resources/read`). */\nexport interface McpResourceDescriptor {\n uri: string\n name: string\n mimeType: 'text/html'\n description?: string\n}\n\n/** An MCP `resources/read` result. */\nexport interface McpResourceContents {\n contents: { uri: string; mimeType: 'text/html'; text: string }[]\n}\n\n/**\n * Declare a `ui://` HTML app resource. Fails fast if the uri is not a `ui://` scheme or the HTML is\n * empty (error-handling.md) — a misconfigured resource is caught at definition time.\n */\nexport function defineAppResource(input: AppResourceInput): AppResource {\n if (!input.uri.startsWith('ui://')) {\n throw new Error(\n `defineAppResource: uri must use the ui:// scheme (an app UI resource). Got: ${JSON.stringify(input.uri)}`,\n )\n }\n if (input.html.length === 0) {\n throw new Error(`defineAppResource(${JSON.stringify(input.uri)}): html must be non-empty.`)\n }\n return {\n uri: input.uri,\n name: input.name,\n mimeType: 'text/html',\n html: input.html,\n ...(input.description !== undefined ? { description: input.description } : {}),\n }\n}\n\n/** Map app resources to MCP `resources/list` descriptors (the HTML body is omitted from the list). */\nexport function buildResourceDescriptors(\n resources: readonly AppResource[],\n): McpResourceDescriptor[] {\n return resources.map((r) => ({\n uri: r.uri,\n name: r.name,\n mimeType: r.mimeType,\n ...(r.description !== undefined ? { description: r.description } : {}),\n }))\n}\n\n/** Serve the HTML for `uri` as an MCP `resources/read` result, or `null` when unknown. */\nexport function readAppResource(\n resources: readonly AppResource[],\n uri: string,\n): McpResourceContents | null {\n const found = resources.find((r) => r.uri === uri)\n if (!found) return null\n return { contents: [{ uri: found.uri, mimeType: 'text/html', text: found.html }] }\n}\n\n/** True when `v` is a well-formed {@link AppResource} (from `defineAppResource`). */\nfunction isAppResource(v: unknown): v is AppResource {\n if (typeof v !== 'object' || v === null) return false\n const r = v as Record<string, unknown>\n return (\n typeof r.uri === 'string' &&\n r.uri.startsWith('ui://') &&\n r.mimeType === 'text/html' &&\n typeof r.html === 'string'\n )\n}\n\n/**\n * M30 wiring — extract the App resources an agent module declares via a named `appResources` export\n * (`export const appResources = [defineAppResource(...)]`). Returns only the well-formed entries; a\n * module without the export (or with a malformed one) yields `[]`. This is how per-agent `ui://`\n * resources reach the MCP server's `resources/list` + `resources/read` without a runtime dependency\n * from `@theokit/agents` on this theo-side type (the module exports them; the serving path reads them).\n */\nexport function extractAppResources(mod: unknown): AppResource[] {\n const raw = (mod as { appResources?: unknown } | null | undefined)?.appResources\n if (!Array.isArray(raw)) return []\n return raw.filter(isAppResource)\n}\n","/**\n * M16 (theokit-ai-first) — serve an agent as an MCP server over HTTP at `/api/agents/<name>/mcp`.\n *\n * Answers the two core MCP methods over JSON-RPC 2.0: `initialize` (server info + capabilities) and\n * `tools/list` (the agent's tools as MCP descriptors, via `buildMcpToolDescriptors`). M30 adds\n * `resources/list` + `resources/read` for `ui://` App resources. Unknown methods return `-32601`\n * (method not found). Web Standards Response (G8). The stdio transport + full method set stay\n * SDK-side (sdk-runtime.md); this exposes the agent over the app's own HTTP route.\n */\nimport { type CompiledTool, compileAgentModule } from '@theokit/agents'\n\nimport { type AppResource, buildResourceDescriptors, readAppResource } from './mcp-app-resources.js'\n\nconst MCP_PATH = /^\\/api\\/agents\\/([^/]+)\\/mcp$/\n\n/**\n * The MCP protocol revision THIS server transport implements (M34). The protocol version is a\n * property of the server that speaks it (this handler), not of the manifest data generator — so the\n * handler owns it. Current MCP revision (`2025-06-18`); replaces the stale `2024-11-05` the manifest\n * generator carried.\n */\nconst MCP_PROTOCOL_VERSION = '2025-06-18'\n\n/** An MCP `tools/list` descriptor with the tool's REAL input schema retained (M34 — no longer dropped). */\ninterface McpToolDescriptor {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\n/** Build tool descriptors from the compiled tools, retaining each tool's real JSON-Schema input (M34). */\nfunction toolDescriptors(tools: readonly CompiledTool[]): McpToolDescriptor[] {\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n // The compiled tool already carries a JSON Schema (via `z.toJSONSchema()` in defineAgentTool).\n // Fall back to a permissive object only when a tool genuinely declared no schema.\n inputSchema:\n t.inputSchema && typeof t.inputSchema === 'object'\n ? (t.inputSchema as Record<string, unknown>)\n : { type: 'object', properties: {} },\n }))\n}\n\n/** An MCP `CallToolResult` — `content[]` + `isError` (the shape MCP clients expect). */\ninterface CallToolResult {\n content: { type: 'text'; text: string }[]\n isError?: boolean\n}\n\n/** Return the agent name when `urlPath` is the MCP endpoint, else `null`. */\nexport function isMcpPath(urlPath: string): string | null {\n const match = MCP_PATH.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0'\n id: number | string | null\n method: string\n params?: unknown\n}\n\nfunction isJsonRpcRequest(body: unknown): body is JsonRpcRequest {\n return (\n typeof body === 'object' &&\n body !== null &&\n (body as { jsonrpc?: unknown }).jsonrpc === '2.0' &&\n typeof (body as { method?: unknown }).method === 'string'\n )\n}\n\nfunction jsonResponse(payload: unknown): Response {\n return new Response(JSON.stringify(payload), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/**\n * Execute an MCP `tools/call` against the compiled tools (M34). Finds the tool by name, runs its\n * handler with the supplied `arguments`, and shapes the result into a `CallToolResult`. An unknown\n * tool or a throwing handler yields `{ isError: true }` (MCP convention — the model sees the failure)\n * rather than a JSON-RPC crash.\n */\nasync function callTool(\n tools: readonly CompiledTool[],\n toolName: unknown,\n args: unknown,\n hitl?: ReadonlyMap<string, unknown>,\n): Promise<CallToolResult> {\n if (typeof toolName !== 'string') {\n return {\n content: [{ type: 'text', text: 'tools/call requires a string `name`.' }],\n isError: true,\n }\n }\n const tool = tools.find((t) => t.name === toolName)\n if (!tool) {\n return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true }\n }\n // #99 — REFUSE a HITL-gated tool. The approval gate (`compiled.hitl`) lives in the SDK run-loop,\n // not in the raw tool handler; executing the handler here would BYPASS the human approval the web/\n // TUI surfaces enforce. Over MCP there is no approval mechanism, so a gated tool is not callable —\n // return an error result instead of running it unguarded (fail-closed, Rule 8).\n if (hitl?.has(toolName)) {\n return {\n content: [\n {\n type: 'text',\n text: `Tool \"${toolName}\" requires human approval, which is not available over MCP. Refused.`,\n },\n ],\n isError: true,\n }\n }\n try {\n const result = await tool.handler(args)\n const text = typeof result === 'string' ? result : JSON.stringify(result)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n return {\n content: [\n { type: 'text', text: err instanceof Error ? err.message : 'Tool execution failed' },\n ],\n isError: true,\n }\n }\n}\n\n/** `resources/read` (M30) — extracted to keep `handleMcpJsonRpc` within the complexity budget. */\nfunction handleResourcesRead(\n id: number | string | null,\n params: unknown,\n appResources: readonly AppResource[],\n): Response {\n const uri = (params as { uri?: unknown } | undefined)?.uri\n if (typeof uri !== 'string') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32602, message: 'resources/read requires a string `uri` param.' },\n })\n }\n const contents = readAppResource(appResources, uri)\n if (contents === null) {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32602, message: `Resource not found: ${uri}` },\n })\n }\n return jsonResponse({ jsonrpc: '2.0', id, result: contents })\n}\n\n/**\n * Handle one MCP JSON-RPC request for an agent module. Always returns a 200 JSON-RPC envelope.\n *\n * M30 — `appResources` (optional) are the agent's declared `ui://` App resources; when present the\n * server advertises `capabilities.resources` and answers `resources/list` + `resources/read`.\n */\nexport async function handleMcpJsonRpc(\n mod: unknown,\n name: string,\n body: unknown,\n appResources: readonly AppResource[] = [],\n): Promise<Response> {\n if (!isJsonRpcRequest(body)) {\n return jsonResponse({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32600, message: 'Invalid Request' },\n })\n }\n const { id, method, params } = body\n try {\n const compiled = compileAgentModule(mod, `mcp server for \"${name}\"`)\n if (method === 'initialize') {\n const capabilities: Record<string, unknown> = { tools: {} }\n if (appResources.length > 0) capabilities.resources = {}\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities,\n serverInfo: { name, version: '1.0' },\n },\n })\n }\n if (method === 'tools/list') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: { tools: toolDescriptors(compiled.tools) },\n })\n }\n if (method === 'tools/call') {\n const p = params as { name?: unknown; arguments?: unknown } | undefined\n const result = await callTool(compiled.tools, p?.name, p?.arguments ?? {}, compiled.hitl)\n return jsonResponse({ jsonrpc: '2.0', id, result })\n }\n if (method === 'resources/list') {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n result: { resources: buildResourceDescriptors(appResources) },\n })\n }\n if (method === 'resources/read') {\n return handleResourcesRead(id, params, appResources)\n }\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32601, message: `Method not found: ${method}` },\n })\n } catch (err) {\n return jsonResponse({\n jsonrpc: '2.0',\n id,\n error: { code: -32603, message: err instanceof Error ? err.message : 'Internal error' },\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/plugins/plugin-runner.ts","../src/server/plugins/load-plugins.ts"],"sourcesContent":["import type {\n HookName,\n HookResult,\n OnErrorHook,\n OnRequestHook,\n OnResponseHook,\n PluginContext,\n PluginErrorContext,\n PreHandlerHook,\n RunHookOptions,\n TheoApp,\n TheoPlugin,\n} from '../plugin-types.js'\n\nexport class DuplicatePluginError extends Error {\n constructor(name: string) {\n super(`Plugin \"${name}\" is already registered.`)\n this.name = 'DuplicatePluginError'\n }\n}\n\n/**\n * @deprecated Per T3.1 (C1 plugin scope encapsulation) — duplicate decoration\n * keys across sibling plugins are now PERMITTED because each plugin gets its\n * own child scope via `Object.create(parent)` (Fastify pattern, ADR-0028\n * blueprint D1). This class is retained for one minor cycle so consumers who\n * `instanceof DuplicateDecorationError` continue to compile; the constructor\n * is no longer reachable from `PluginRunner.register()`. Removal scheduled\n * for 0.x+2 per CHANGELOG.\n */\nexport class DuplicateDecorationError extends Error {\n constructor(key: string, existingPlugin: string, newPlugin: string) {\n super(\n `Plugin \"${newPlugin}\" tried to decorate ctx.${key}, but it is already declared by plugin \"${existingPlugin}\".`,\n )\n this.name = 'DuplicateDecorationError'\n }\n}\n\n/**\n * Per-plugin scope built by `Object.create(parentApp)` at `register()` time\n * (T3.1 / Fastify `plugin-override.js:38` pattern). Each plugin's\n * `decorateRequest` calls populate THIS scope's `decorations` map only —\n * parent + sibling scopes are isolated through the JavaScript prototype chain.\n */\ninterface PluginScope {\n /** The child TheoApp instance whose proto chain points at `parentApp`. */\n app: TheoApp\n /** Per-plugin decorations map. Cross-plugin key collision is permitted. */\n decorations: Map<string, unknown>\n}\n\nexport class PluginRunner {\n private plugins = new Set<string>()\n private pluginScopes = new Map<string, PluginScope>()\n private onRequestHooks: OnRequestHook[] = []\n private preHandlerHooks: PreHandlerHook[] = []\n private onResponseHooks: OnResponseHook[] = []\n private onErrorHooks: OnErrorHook[] = []\n\n /**\n * Parent app — proto-chain root for all child scopes. Has its OWN empty\n * decorations map (parent never receives `decorateRequest` calls under\n * the T3.1 contract; only child scopes do).\n */\n private parentDecorations = new Map<string, unknown>()\n private parentApp: TheoApp = this.buildParentAppFacade()\n\n has(name: string): boolean {\n return this.plugins.has(name)\n }\n\n /**\n * Per T3.1 (C1 plugin scope encapsulation):\n * 1. Reserve the plugin name (still rejects duplicates).\n * 2. Build a CHILD TheoApp via `Object.create(parentApp)` — Fastify\n * `plugin-override.js:38` pattern. The child's own `decorateRequest`\n * populates per-scope `decorations`; the child's `addHook` still\n * forwards into the shared hook lists (hooks ARE process-global —\n * only decorations are scoped, mirroring Fastify's decoration vs\n * hook semantics).\n * 3. Invoke `plugin.register(childApp)`.\n *\n * Cross-plugin decoration-key collisions are PERMITTED (per blueprint\n * D1). The legacy `DuplicateDecorationError` is no longer thrown.\n */\n async register(plugin: TheoPlugin): Promise<void> {\n if (this.plugins.has(plugin.name)) {\n throw new DuplicatePluginError(plugin.name)\n }\n // Add to registry FIRST so duplicate detection works even if register throws.\n this.plugins.add(plugin.name)\n\n const scope = this.buildPluginScope(plugin.name)\n this.pluginScopes.set(plugin.name, scope)\n\n try {\n await plugin.register(scope.app)\n } catch (err) {\n // Roll back partial registration so a failed plugin does not leave\n // half-mounted state (T1.1 BDD \"error scenario: plugin throws in\n // register → parent recovery is clean\").\n this.plugins.delete(plugin.name)\n this.pluginScopes.delete(plugin.name)\n throw err\n }\n }\n\n /**\n * Build the parent app facade. Hooks forward to shared lists.\n * `decorateRequest` writes into the parent decorations map — used only\n * if a future consumer registers a non-plugin \"app-level\" decoration\n * directly. T3.1 contract: plugins decorate ONLY through child scopes.\n */\n private buildParentAppFacade(): TheoApp {\n const facade: TheoApp = {\n addHook: (name: HookName, fn: unknown) => {\n this.routeHookByName(name, fn)\n },\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the per-decoration type for consumers\n decorateRequest: <T>(key: string, value: T) => {\n this.parentDecorations.set(key, value)\n },\n }\n return facade\n }\n\n /**\n * Build a child scope via `Object.create(parentApp)` so the child\n * inherits the parent's facade methods through the prototype chain.\n * Then override `decorateRequest` on the child instance so writes\n * land in the per-scope `decorations` map (parent is NOT mutated).\n */\n private buildPluginScope(_pluginName: string): PluginScope {\n const decorations = new Map<string, unknown>()\n const childApp: TheoApp = Object.create(this.parentApp) as TheoApp\n // Override decorateRequest on the child instance — parent's facade\n // method stays untouched, so `getParentApp().decorateRequest` still\n // writes to parentDecorations. This is the canonical Fastify pattern.\n // `_pluginName` reserved for future per-scope diagnostics (underscore\n // prefix marks intentional unused param per project lint convention).\n Object.defineProperty(childApp, 'decorateRequest', {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the per-decoration type for consumers\n value: <T>(key: string, value: T) => {\n if (typeof key !== 'string') {\n throw new TypeError(\n `decorateRequest: invalid key (expected string, got ${typeof key}). ` +\n `Plugin authors MUST pass string keys.`,\n )\n }\n decorations.set(key, value)\n },\n enumerable: false,\n writable: false,\n configurable: false,\n })\n // Expose `decorations` on the child instance so `getPluginScope()`\n // consumers (devtools, tests) can introspect without touching this\n // private map. Read-only property — the JS prototype chain rule means\n // child.decorations shadows any parent.decorations the proto exposes.\n Object.defineProperty(childApp, 'decorations', {\n get: () => Object.fromEntries(decorations),\n enumerable: false,\n configurable: false,\n })\n return { app: childApp, decorations }\n }\n\n private routeHookByName(name: HookName, fn: unknown): void {\n switch (name) {\n case 'onRequest':\n this.onRequestHooks.push(fn as OnRequestHook)\n return\n case 'preHandler':\n this.preHandlerHooks.push(fn as PreHandlerHook)\n return\n case 'onResponse':\n this.onResponseHooks.push(fn as OnResponseHook)\n return\n case 'onError':\n this.onErrorHooks.push(fn as OnErrorHook)\n return\n }\n }\n\n /**\n * Apply ALL plugin scopes' decorations into the request ctx. Per T3.1,\n * sibling plugins MAY decorate the same key — last-writer-wins at apply\n * time (ordering = registration order). This keeps the legacy\n * `ctx.<key>` flat surface working for consumers that already aggregate\n * decorations into a single bag.\n */\n applyDecorations(ctx: Record<string, unknown>): void {\n for (const scope of this.pluginScopes.values()) {\n for (const [key, value] of scope.decorations.entries()) {\n ctx[key] = value\n }\n }\n }\n\n /**\n * Apply ONLY one plugin's scoped decorations into `target`. Used by\n * the T1.1 RED-3 test (sibling isolation proof) and by future scope-aware\n * dispatch paths. Throws if the plugin name isn't registered.\n */\n applyScopedDecorations(pluginName: string, target: Record<string, unknown>): void {\n const scope = this.pluginScopes.get(pluginName)\n if (!scope) throw new Error(`PluginRunner: unknown plugin \"${pluginName}\"`)\n for (const [key, value] of scope.decorations.entries()) {\n target[key] = value\n }\n }\n\n /** T1.1 RED-1/RED-4 introspection: returns the child TheoApp built for `pluginName`. */\n getPluginScope(pluginName: string): TheoApp {\n const scope = this.pluginScopes.get(pluginName)\n if (!scope) throw new Error(`PluginRunner: unknown plugin \"${pluginName}\"`)\n return scope.app\n }\n\n /** T1.1 RED-4: returns the parent TheoApp (root of every child's proto chain). */\n getParentApp(): TheoApp {\n return this.parentApp\n }\n\n /** T1.1 RED-2: returns the parent's decorations map (NEVER touched by plugin decorate calls under T3.1 contract). */\n getParentDecorations(): Map<string, unknown> {\n return this.parentDecorations\n }\n\n async runOnRequest(ctx: PluginContext): Promise<HookResult> {\n return this.runHookList(this.onRequestHooks, ctx)\n }\n\n async runPreHandler(ctx: PluginContext): Promise<HookResult> {\n return this.runHookList(this.preHandlerHooks, ctx)\n }\n\n async runOnResponse(ctx: PluginContext, options: RunHookOptions = {}): Promise<HookResult> {\n return this.runHookList(this.onResponseHooks, ctx, options)\n }\n\n /**\n * Run all onError hooks. Swallows errors thrown inside hooks themselves to\n * prevent recursion (an error in an error handler must not trigger onError\n * again).\n */\n async runOnError(ctx: PluginContext, error: unknown): Promise<HookResult> {\n const errorCtx: PluginErrorContext = { ...ctx, error }\n for (const hook of this.onErrorHooks) {\n try {\n await hook(errorCtx)\n } catch (innerErr) {\n // EC-9 + onError-safety: swallow to avoid recursion. Diagnostic\n // surface only — error context is preserved via innerErr arg.\n console.error(\n `[plugin-runner] onError hook threw; suppressed to avoid recursion:`,\n innerErr,\n )\n }\n }\n return { shortCircuited: false }\n }\n\n private async runHookList(\n hooks: readonly ((ctx: PluginContext) => void | Promise<void>)[],\n ctx: PluginContext,\n options: RunHookOptions = {},\n ): Promise<HookResult> {\n for (const hook of hooks) {\n try {\n await hook(ctx)\n } catch (err) {\n if (options.inErrorPath) {\n // EC-9: we're already handling an error; do NOT trigger onError again.\n // Diagnostic surface only — original error preserved via err arg.\n console.error(`[plugin-runner] hook threw during error path; suppressed (EC-9):`, err)\n continue\n }\n throw err\n }\n if (ctx.response.writableEnded || ctx.response.headersSent) {\n return { shortCircuited: true }\n }\n }\n return { shortCircuited: false }\n }\n}\n","import type { TheoPlugin } from '../plugin-types.js'\n\nimport { PluginRunner } from './plugin-runner.js'\n\nexport class InvalidPluginShapeError extends Error {\n constructor(index: number, reason: string) {\n super(`plugins[${index}] is not a valid TheoPlugin: ${reason}`)\n this.name = 'InvalidPluginShapeError'\n }\n}\n\nfunction isPlugin(value: unknown, index: number): value is TheoPlugin {\n if (value == null || typeof value !== 'object') {\n throw new InvalidPluginShapeError(index, 'expected an object')\n }\n const v = value as Record<string, unknown>\n if (typeof v.name !== 'string' || v.name.length === 0) {\n throw new InvalidPluginShapeError(index, 'missing \"name\" string')\n }\n if (typeof v.register !== 'function') {\n throw new InvalidPluginShapeError(index, 'missing \"register\" function')\n }\n return true\n}\n\n/**\n * Build a PluginRunner from a list of plugins typically declared in\n * `theo.config.ts` under the `plugins` field. Returns `undefined` when no\n * plugins are configured so callers can pass `undefined` to `executeRoute`\n * and preserve the zero-overhead path.\n */\nexport async function createPluginRunnerFromConfig(\n plugins: unknown,\n): Promise<PluginRunner | undefined> {\n if (plugins == null) return undefined\n if (!Array.isArray(plugins)) return undefined\n if (plugins.length === 0) return undefined\n\n const runner = new PluginRunner()\n const pluginsArray: unknown[] = plugins\n for (let i = 0; i < pluginsArray.length; i++) {\n const candidate: unknown = pluginsArray[i]\n if (!isPlugin(candidate, i)) {\n // Unreachable in practice — `isPlugin` throws on invalid input.\n // The branch exists to narrow `candidate` to TheoPlugin below.\n continue\n }\n await runner.register(candidate)\n }\n return runner\n}\n"],"mappings":"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","names":[]}