theokit 0.4.0-beta.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/dist/{actions-virtual-module-HWNTIEPI.js → actions-virtual-module-IY46EEEX.js} +2 -2
  2. package/dist/{actions-virtual-module-SEIPACG5.js → actions-virtual-module-XNHDNCZ6.js} +2 -2
  3. package/dist/add-2ZFFGGE4.js +0 -0
  4. package/dist/{app-typed-client-ITIYTBXO.js → app-typed-client-OUJO3V5J.js} +10 -5
  5. package/dist/{app-typed-client-62FYBLVJ.js.map → app-typed-client-OUJO3V5J.js.map} +1 -1
  6. package/dist/{app-typed-client-62FYBLVJ.js → app-typed-client-YOMWSA3F.js} +11 -6
  7. package/dist/{app-typed-client-ITIYTBXO.js.map → app-typed-client-YOMWSA3F.js.map} +1 -1
  8. package/dist/audit-log-BQWM5YLG.d.ts +60 -0
  9. package/dist/{aws-lambda-XYCGZH3I.js → aws-lambda-GKSTX6HD.js} +3 -3
  10. package/dist/body-parser-web-MUWBKZ3F.js +70 -0
  11. package/dist/body-parser-web-MUWBKZ3F.js.map +1 -0
  12. package/dist/broadcast-QOQGUNNK.js +0 -0
  13. package/dist/{build-HIGHJQQV.js → build-D4J7XECU.js} +31 -22
  14. package/dist/build-D4J7XECU.js.map +1 -0
  15. package/dist/build-request-body-preview-II2235E6.js +0 -0
  16. package/dist/{bun-K73TQEWC.js → bun-ISHSNFIO.js} +3 -3
  17. package/dist/{check-PZR67OY5.js → check-NXYPLM6M.js} +2 -2
  18. package/dist/{chunk-WZ7CPVQB.js → chunk-27LWMYNK.js} +28 -24
  19. package/dist/chunk-27LWMYNK.js.map +1 -0
  20. package/dist/{chunk-BIGBFZSU.js → chunk-2F4FROEQ.js} +1689 -1521
  21. package/dist/chunk-2F4FROEQ.js.map +1 -0
  22. package/dist/chunk-4HBI7DHP.js +20 -0
  23. package/dist/chunk-4HBI7DHP.js.map +1 -0
  24. package/dist/{chunk-X4JAX2U3.js → chunk-4S2UCILN.js} +180 -125
  25. package/dist/chunk-4S2UCILN.js.map +1 -0
  26. package/dist/{chunk-C52GSJPF.js → chunk-4S6YHNG2.js} +11 -8
  27. package/dist/chunk-4S6YHNG2.js.map +1 -0
  28. package/dist/chunk-5ODOE6EF.js +0 -0
  29. package/dist/{chunk-KJVEJILQ.js → chunk-5PU65T5W.js} +9 -3
  30. package/dist/chunk-5PU65T5W.js.map +1 -0
  31. package/dist/chunk-5QW7IQQU.js +36 -0
  32. package/dist/chunk-5QW7IQQU.js.map +1 -0
  33. package/dist/chunk-5Z2727GV.js +1324 -0
  34. package/dist/chunk-5Z2727GV.js.map +1 -0
  35. package/dist/{chunk-YLBSSEHX.js → chunk-6NEXVBPY.js} +5 -15
  36. package/dist/chunk-6NEXVBPY.js.map +1 -0
  37. package/dist/chunk-7MQOHNHE.js +36 -0
  38. package/dist/chunk-7MQOHNHE.js.map +1 -0
  39. package/dist/{chunk-TPCT3MXV.js → chunk-ABVITXFH.js} +405 -272
  40. package/dist/chunk-ABVITXFH.js.map +1 -0
  41. package/dist/chunk-ASDXVRJD.js +185 -0
  42. package/dist/chunk-ASDXVRJD.js.map +1 -0
  43. package/dist/chunk-B3L3TJVF.js +406 -0
  44. package/dist/chunk-B3L3TJVF.js.map +1 -0
  45. package/dist/{chunk-PB4GR7EP.js → chunk-C3ZZ56YZ.js} +1 -18
  46. package/dist/chunk-C3ZZ56YZ.js.map +1 -0
  47. package/dist/{chunk-O7335ZGH.js → chunk-CG563V5M.js} +5 -3
  48. package/dist/chunk-CG563V5M.js.map +1 -0
  49. package/dist/{chunk-5OFPQK6N.js → chunk-COXLEZCN.js} +2 -1
  50. package/dist/chunk-COXLEZCN.js.map +1 -0
  51. package/dist/{chunk-O4BLVPML.js → chunk-DNMSV3R3.js} +22 -28
  52. package/dist/chunk-DNMSV3R3.js.map +1 -0
  53. package/dist/chunk-E3JH6YUM.js +1 -0
  54. package/dist/chunk-ESC54TAK.js +220 -0
  55. package/dist/chunk-ESC54TAK.js.map +1 -0
  56. package/dist/chunk-FI7MPTJW.js +77 -0
  57. package/dist/chunk-FI7MPTJW.js.map +1 -0
  58. package/dist/chunk-H4J2LECY.js +201 -0
  59. package/dist/chunk-H4J2LECY.js.map +1 -0
  60. package/dist/chunk-IAJ2JVEH.js +256 -0
  61. package/dist/chunk-IAJ2JVEH.js.map +1 -0
  62. package/dist/{chunk-F3TG5FJV.js → chunk-IEZCAT2I.js} +7 -1
  63. package/dist/{chunk-F3TG5FJV.js.map → chunk-IEZCAT2I.js.map} +1 -1
  64. package/dist/chunk-JHEAWR3L.js +1 -0
  65. package/dist/chunk-JQSKBMXP.js +17 -0
  66. package/dist/chunk-JQSKBMXP.js.map +1 -0
  67. package/dist/{chunk-4QTKSLTO.js → chunk-K4M64WD6.js} +9 -5
  68. package/dist/{chunk-4QTKSLTO.js.map → chunk-K4M64WD6.js.map} +1 -1
  69. package/dist/chunk-KI7OWQUX.js +293 -0
  70. package/dist/chunk-KI7OWQUX.js.map +1 -0
  71. package/dist/chunk-KT3MWNZG.js +0 -0
  72. package/dist/{chunk-ZJW5FFYJ.js → chunk-LC5PYNJP.js} +2 -2
  73. package/dist/{chunk-JAAHOGKI.js → chunk-M2EY7KDR.js} +1223 -1124
  74. package/dist/chunk-M2EY7KDR.js.map +1 -0
  75. package/dist/{chunk-2VQKDRVF.js → chunk-MR7OLIC6.js} +3 -3
  76. package/dist/chunk-NM4WF3XD.js +63 -0
  77. package/dist/chunk-NM4WF3XD.js.map +1 -0
  78. package/dist/chunk-NPMCKOX4.js +1 -0
  79. package/dist/{chunk-VRHXOKRP.js → chunk-NZXH2LQQ.js} +86 -83
  80. package/dist/chunk-NZXH2LQQ.js.map +1 -0
  81. package/dist/{chunk-B6RP57M3.js → chunk-OLPB36O2.js} +4 -4
  82. package/dist/chunk-PIVX3DYW.js +142 -0
  83. package/dist/chunk-PIVX3DYW.js.map +1 -0
  84. package/dist/{chunk-7TOMTMHT.js → chunk-R7OC6UDK.js} +2 -1
  85. package/dist/chunk-R7OC6UDK.js.map +1 -0
  86. package/dist/chunk-RESN62GB.js +269 -0
  87. package/dist/chunk-RESN62GB.js.map +1 -0
  88. package/dist/{chunk-64JI5LTO.js → chunk-RMU7EBRO.js} +2 -2
  89. package/dist/chunk-RMU7EBRO.js.map +1 -0
  90. package/dist/chunk-TQYSPYKU.js +131 -0
  91. package/dist/chunk-TQYSPYKU.js.map +1 -0
  92. package/dist/chunk-TSLSIRBR.js +107 -0
  93. package/dist/chunk-TSLSIRBR.js.map +1 -0
  94. package/dist/{chunk-XP7YXRHO.js → chunk-U6TPSW4L.js} +37 -5
  95. package/dist/chunk-U6TPSW4L.js.map +1 -0
  96. package/dist/chunk-UD3LFGDL.js +45 -0
  97. package/dist/chunk-UD3LFGDL.js.map +1 -0
  98. package/dist/chunk-UUFYP2UM.js +161 -0
  99. package/dist/chunk-UUFYP2UM.js.map +1 -0
  100. package/dist/{chunk-OXIQI2XZ.js → chunk-VBZCVFSA.js} +2 -2
  101. package/dist/chunk-VMEWD57H.js +137 -0
  102. package/dist/chunk-VMEWD57H.js.map +1 -0
  103. package/dist/{chunk-TRH2L3FI.js → chunk-WEGALZEU.js} +3 -3
  104. package/dist/{chunk-ZOXNJV2O.js → chunk-WGHJD6I7.js} +35 -138
  105. package/dist/chunk-WGHJD6I7.js.map +1 -0
  106. package/dist/chunk-XMS5VRIK.js +0 -0
  107. package/dist/chunk-Y4H3JNVK.js +18 -0
  108. package/dist/chunk-Y4H3JNVK.js.map +1 -0
  109. package/dist/chunk-Z5IGLBWE.js +329 -0
  110. package/dist/chunk-Z5IGLBWE.js.map +1 -0
  111. package/dist/chunk-ZEGYW52B.js +26 -0
  112. package/dist/chunk-ZEGYW52B.js.map +1 -0
  113. package/dist/chunk-ZJQ4O76G.js +87 -0
  114. package/dist/chunk-ZJQ4O76G.js.map +1 -0
  115. package/dist/cli/index.js +32 -21
  116. package/dist/cli/index.js.map +1 -1
  117. package/dist/client/index.d.ts +107 -1
  118. package/dist/client/index.js +152 -6
  119. package/dist/client/index.js.map +1 -1
  120. package/dist/{cloudflare-LCAOTRYZ.js → cloudflare-OJGJ7R2D.js} +3 -3
  121. package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
  122. package/dist/cookies-MJKMGJ7N.js +22 -0
  123. package/dist/csrf-BBrEZSBW.d.ts +107 -0
  124. package/dist/csrf-readiness-store-CjIoub3U.d.ts +43 -0
  125. package/dist/define-websocket-CdK94O-D.d.ts +64 -0
  126. package/dist/{deno-deploy-ZN4RE5HF.js → deno-deploy-BHWKFTOW.js} +3 -3
  127. package/dist/{dev-266MVCVA.js → dev-MJVSRZGF.js} +11 -11
  128. package/dist/{dev-emit-RBSFZZNO.js → dev-emit-5VPRCHOD.js} +39 -142
  129. package/dist/dev-emit-5VPRCHOD.js.map +1 -0
  130. package/dist/{dev-emit-NO6OZJOQ.js → dev-emit-HHP2XJXE.js} +5 -5
  131. package/dist/devtools/entry.js +185 -131
  132. package/dist/devtools/entry.js.map +1 -1
  133. package/dist/{dispatcher-AQJGI3HU.js → dispatcher-63LBXYLE.js} +2 -2
  134. package/dist/{dispatcher-E6QV32BO.js → dispatcher-EJME6C6M.js} +5 -2
  135. package/dist/dispatcher-EJME6C6M.js.map +1 -0
  136. package/dist/{dist-5V77Z223.js → dist-KRW6OVD4.js} +7 -7
  137. package/dist/dist-KRW6OVD4.js.map +1 -0
  138. package/dist/docker-EZDA5FT7.js +0 -0
  139. package/dist/error-envelope-BsNzzAV5.d.ts +62 -0
  140. package/dist/{generate-DT4IIAHF.js → generate-AZ2K6Z2Z.js} +75 -2
  141. package/dist/generate-AZ2K6Z2Z.js.map +1 -0
  142. package/dist/index-DWWEdFh5.d.ts +399 -0
  143. package/dist/index.d.ts +161 -1391
  144. package/dist/index.js +20 -8
  145. package/dist/index.js.map +1 -1
  146. package/dist/{info-FJ4NXKTB.js → info-47VB62MV.js} +5 -5
  147. package/dist/job-backend-CgC8Xf33.d.ts +68 -0
  148. package/dist/{lib-NL5JXGEB.js → lib-NST3PNZX.js} +31 -31
  149. package/dist/lib-NST3PNZX.js.map +1 -0
  150. package/dist/module-loader-Cfz7dgCP.d.ts +26 -0
  151. package/dist/{netlify-5VQ22Z2P.js → netlify-GSNSJGMN.js} +3 -3
  152. package/dist/{node-3APTLGWB.js → node-6I5B6RL3.js} +3 -3
  153. package/dist/node-6I5B6RL3.js.map +1 -0
  154. package/dist/{openapi-FNVSWZOL.js → openapi-NFLSNNVQ.js} +10 -10
  155. package/dist/plugin-runner-BGBkzgi0.d.ts +95 -0
  156. package/dist/plugin-types-DNJGxr4Z.d.ts +79 -0
  157. package/dist/{proper-lockfile-4Y3DP3RP.js → proper-lockfile-MVKMQGFK.js} +27 -27
  158. package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
  159. package/dist/rate-limit-BdNDZ3vt.d.ts +58 -0
  160. package/dist/registry-QHEZ3BWB.js +25 -0
  161. package/dist/router-RGZFX75G.js +0 -0
  162. package/dist/{routes-H4SPLFHJ.js → routes-3A4XGIEJ.js} +6 -6
  163. package/dist/{scan-C2BOKLSY.js → scan-NQFI5S7P.js} +2 -2
  164. package/dist/scan-NQFI5S7P.js.map +1 -0
  165. package/dist/schema-D3v8VB7o.d.ts +76 -0
  166. package/dist/{schema-VR6YNVLQ.js → schema-KZVOV333.js} +5 -3
  167. package/dist/schema-KZVOV333.js.map +1 -0
  168. package/dist/server/agent/index.d.ts +229 -0
  169. package/dist/server/agent/index.js +16 -0
  170. package/dist/server/agent/index.js.map +1 -0
  171. package/dist/server/auth/index.d.ts +46 -1
  172. package/dist/server/auth/index.js +10 -3
  173. package/dist/server/cost/index.d.ts +1 -3
  174. package/dist/server/cost/index.js +1 -1
  175. package/dist/server/cron/index.js +4 -2
  176. package/dist/server/define/index.d.ts +281 -0
  177. package/dist/server/define/index.js +26 -0
  178. package/dist/server/define/index.js.map +1 -0
  179. package/dist/server/http/index.d.ts +10 -0
  180. package/dist/server/http/index.js +74 -0
  181. package/dist/server/http/index.js.map +1 -0
  182. package/dist/server/index.d.ts +151 -1677
  183. package/dist/server/index.js +558 -1102
  184. package/dist/server/index.js.map +1 -1
  185. package/dist/server/jobs/index.d.ts +348 -2
  186. package/dist/server/jobs/index.js +5 -3
  187. package/dist/server/observability/index.d.ts +324 -0
  188. package/dist/server/observability/index.js +48 -0
  189. package/dist/server/observability/index.js.map +1 -0
  190. package/dist/server/plugins/index.d.ts +17 -0
  191. package/dist/server/plugins/index.js +17 -0
  192. package/dist/server/plugins/index.js.map +1 -0
  193. package/dist/server/rate-limit/index.d.ts +105 -0
  194. package/dist/server/rate-limit/index.js +25 -0
  195. package/dist/server/rate-limit/index.js.map +1 -0
  196. package/dist/server/realtime/index.d.ts +15 -0
  197. package/dist/server/realtime/index.js +8 -0
  198. package/dist/server/realtime/index.js.map +1 -0
  199. package/dist/server/scan/index.d.ts +106 -0
  200. package/dist/server/scan/index.js +43 -0
  201. package/dist/server/scan/index.js.map +1 -0
  202. package/dist/server/security/index.d.ts +193 -0
  203. package/dist/server/security/index.js +50 -0
  204. package/dist/server/security/index.js.map +1 -0
  205. package/dist/server/storage/index.d.ts +22 -0
  206. package/dist/server/storage/index.js +14 -0
  207. package/dist/server/storage/index.js.map +1 -0
  208. package/dist/server/webhook/index.d.ts +148 -0
  209. package/dist/server/webhook/index.js +19 -0
  210. package/dist/server/webhook/index.js.map +1 -0
  211. package/dist/server-error-to-envelope-NO4CCAFQ.js +8 -0
  212. package/dist/server-error-to-envelope-NO4CCAFQ.js.map +1 -0
  213. package/dist/server-error-to-envelope-UJK6OWDJ.js +134 -0
  214. package/dist/server-error-to-envelope-UJK6OWDJ.js.map +1 -0
  215. package/dist/server-routes-hmr-GZJGPWMS.js +0 -0
  216. package/dist/services-json-Z2QNGVPW.js +142 -0
  217. package/dist/services-json-Z2QNGVPW.js.map +1 -0
  218. package/dist/{services-typed-client-XWYYBWGW.js → services-typed-client-KK6RHRDG.js} +2 -2
  219. package/dist/{services-typed-client-ZX5JUHH3.js → services-typed-client-T7M5VPBP.js} +2 -2
  220. package/dist/{sqlite-vec-ARPNUBWT.js → sqlite-vec-54KB4RD3.js} +2 -2
  221. package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
  222. package/dist/{start-HX3QUSOS.js → start-CGSZPBAG.js} +23 -23
  223. package/dist/start-CGSZPBAG.js.map +1 -0
  224. package/dist/{static-TZBVBDTB.js → static-QI5NQT2X.js} +6 -6
  225. package/dist/storage-manager-C4jsO0Tp.d.ts +89 -0
  226. package/dist/storage-manager-D5KBXXKB.js +0 -0
  227. package/dist/{storage-types-DtIVejC1.d.ts → storage-types-DsDTCPbp.d.ts} +1 -1
  228. package/dist/{theo-cloud-3K4DGB3S.js → theo-cloud-RSQCBNXL.js} +4 -4
  229. package/dist/theo-cloud-RSQCBNXL.js.map +1 -0
  230. package/dist/upgrade-readiness-GUJBCJIS.js +0 -0
  231. package/dist/{vercel-MWW24ABC.js → vercel-TKPZK62A.js} +3 -3
  232. package/dist/vite-plugin/index.d.ts +3 -1
  233. package/dist/vite-plugin/index.js +20 -8
  234. package/dist/{vite-plugin-IK3NOWXS.js → vite-plugin-CR4JDFUQ.js} +9 -9
  235. package/dist/vite-plugin-CR4JDFUQ.js.map +1 -0
  236. package/package.json +59 -10
  237. package/LICENSE +0 -201
  238. package/dist/build-HIGHJQQV.js.map +0 -1
  239. package/dist/chunk-5OFPQK6N.js.map +0 -1
  240. package/dist/chunk-64JI5LTO.js.map +0 -1
  241. package/dist/chunk-7TOMTMHT.js.map +0 -1
  242. package/dist/chunk-BIGBFZSU.js.map +0 -1
  243. package/dist/chunk-C52GSJPF.js.map +0 -1
  244. package/dist/chunk-CZM6WWWS.js +0 -2450
  245. package/dist/chunk-CZM6WWWS.js.map +0 -1
  246. package/dist/chunk-JAAHOGKI.js.map +0 -1
  247. package/dist/chunk-KJVEJILQ.js.map +0 -1
  248. package/dist/chunk-O4BLVPML.js.map +0 -1
  249. package/dist/chunk-O7335ZGH.js.map +0 -1
  250. package/dist/chunk-PB4GR7EP.js.map +0 -1
  251. package/dist/chunk-TPCT3MXV.js.map +0 -1
  252. package/dist/chunk-VRHXOKRP.js.map +0 -1
  253. package/dist/chunk-WZ7CPVQB.js.map +0 -1
  254. package/dist/chunk-X4JAX2U3.js.map +0 -1
  255. package/dist/chunk-XP7YXRHO.js.map +0 -1
  256. package/dist/chunk-YLBSSEHX.js.map +0 -1
  257. package/dist/chunk-ZOXNJV2O.js.map +0 -1
  258. package/dist/dev-emit-RBSFZZNO.js.map +0 -1
  259. package/dist/dispatcher-E6QV32BO.js.map +0 -1
  260. package/dist/dist-5V77Z223.js.map +0 -1
  261. package/dist/generate-DT4IIAHF.js.map +0 -1
  262. package/dist/index-li83Swxa.d.ts +0 -506
  263. package/dist/lib-NL5JXGEB.js.map +0 -1
  264. package/dist/proper-lockfile-4Y3DP3RP.js.map +0 -1
  265. package/dist/registry-4MZ26TZD.js +0 -25
  266. package/dist/schema-CjVly9c_.d.ts +0 -274
  267. package/dist/sqlite-vec-ARPNUBWT.js.map +0 -1
  268. package/dist/start-HX3QUSOS.js.map +0 -1
  269. package/dist/theo-cloud-3K4DGB3S.js.map +0 -1
  270. /package/dist/{actions-virtual-module-HWNTIEPI.js.map → actions-virtual-module-IY46EEEX.js.map} +0 -0
  271. /package/dist/{actions-virtual-module-SEIPACG5.js.map → actions-virtual-module-XNHDNCZ6.js.map} +0 -0
  272. /package/dist/{aws-lambda-XYCGZH3I.js.map → aws-lambda-GKSTX6HD.js.map} +0 -0
  273. /package/dist/{bun-K73TQEWC.js.map → bun-ISHSNFIO.js.map} +0 -0
  274. /package/dist/{check-PZR67OY5.js.map → check-NXYPLM6M.js.map} +0 -0
  275. /package/dist/{dispatcher-AQJGI3HU.js.map → chunk-E3JH6YUM.js.map} +0 -0
  276. /package/dist/{node-3APTLGWB.js.map → chunk-JHEAWR3L.js.map} +0 -0
  277. /package/dist/{chunk-ZJW5FFYJ.js.map → chunk-LC5PYNJP.js.map} +0 -0
  278. /package/dist/{chunk-2VQKDRVF.js.map → chunk-MR7OLIC6.js.map} +0 -0
  279. /package/dist/{scan-C2BOKLSY.js.map → chunk-NPMCKOX4.js.map} +0 -0
  280. /package/dist/{chunk-B6RP57M3.js.map → chunk-OLPB36O2.js.map} +0 -0
  281. /package/dist/{chunk-OXIQI2XZ.js.map → chunk-VBZCVFSA.js.map} +0 -0
  282. /package/dist/{chunk-TRH2L3FI.js.map → chunk-WEGALZEU.js.map} +0 -0
  283. /package/dist/{cloudflare-LCAOTRYZ.js.map → cloudflare-OJGJ7R2D.js.map} +0 -0
  284. /package/dist/{schema-VR6YNVLQ.js.map → cookies-MJKMGJ7N.js.map} +0 -0
  285. /package/dist/{deno-deploy-ZN4RE5HF.js.map → deno-deploy-BHWKFTOW.js.map} +0 -0
  286. /package/dist/{dev-266MVCVA.js.map → dev-MJVSRZGF.js.map} +0 -0
  287. /package/dist/{dev-emit-NO6OZJOQ.js.map → dev-emit-HHP2XJXE.js.map} +0 -0
  288. /package/dist/{vite-plugin-IK3NOWXS.js.map → dispatcher-63LBXYLE.js.map} +0 -0
  289. /package/dist/{info-FJ4NXKTB.js.map → info-47VB62MV.js.map} +0 -0
  290. /package/dist/{netlify-5VQ22Z2P.js.map → netlify-GSNSJGMN.js.map} +0 -0
  291. /package/dist/{openapi-FNVSWZOL.js.map → openapi-NFLSNNVQ.js.map} +0 -0
  292. /package/dist/{registry-4MZ26TZD.js.map → registry-QHEZ3BWB.js.map} +0 -0
  293. /package/dist/{routes-H4SPLFHJ.js.map → routes-3A4XGIEJ.js.map} +0 -0
  294. /package/dist/{services-typed-client-XWYYBWGW.js.map → services-typed-client-KK6RHRDG.js.map} +0 -0
  295. /package/dist/{services-typed-client-ZX5JUHH3.js.map → services-typed-client-T7M5VPBP.js.map} +0 -0
  296. /package/dist/{static-TZBVBDTB.js.map → static-QI5NQT2X.js.map} +0 -0
  297. /package/dist/{vercel-MWW24ABC.js.map → vercel-TKPZK62A.js.map} +0 -0
@@ -1,2450 +0,0 @@
1
- import {
2
- AuthRequiredError
3
- } from "./chunk-PB4GR7EP.js";
4
- import {
5
- DuplicateContextKeyError,
6
- createOutbox,
7
- createOutboxDispatcher,
8
- createQueueClient
9
- } from "./chunk-GY5Q27BJ.js";
10
-
11
- // src/config/load-env.ts
12
- import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
13
- import { resolve } from "path";
14
- import dotenv from "dotenv";
15
- import { expand } from "dotenv-expand";
16
- var MAX_ENV_FILE_BYTES = 1048576;
17
- var cache = /* @__PURE__ */ new Map();
18
- function _resetEnvCache() {
19
- cache.clear();
20
- }
21
- function envFilesInPriorityOrder(mode) {
22
- const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`];
23
- if (mode === "test") {
24
- return files.filter((f) => f !== ".env.local");
25
- }
26
- return files;
27
- }
28
- function readEnvFile(path) {
29
- let stat;
30
- try {
31
- stat = statSync(path);
32
- } catch {
33
- return null;
34
- }
35
- if (!stat.isFile() && !stat.isFIFO()) return null;
36
- if (stat.size > MAX_ENV_FILE_BYTES) {
37
- console.warn(
38
- `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes \u2014 skipping (likely a generated artifact, not a real env file)`
39
- );
40
- return null;
41
- }
42
- try {
43
- const lstat = lstatSync(path);
44
- if (lstat.isSymbolicLink()) {
45
- const real = realpathSync(path);
46
- console.info(`[theokit] .env at ${path} is a symlink \u2192 ${real}`);
47
- }
48
- } catch {
49
- }
50
- try {
51
- const content = readFileSync(path, "utf-8");
52
- return dotenv.parse(content);
53
- } catch {
54
- return null;
55
- }
56
- }
57
- function loadEnv(options = {}) {
58
- const cwd = options.cwd ?? process.cwd();
59
- const mode = options.mode ?? process.env.NODE_ENV ?? "development";
60
- const cacheKey = `${cwd}:${mode}`;
61
- if (!options.forceReload) {
62
- const cached = cache.get(cacheKey);
63
- if (cached) return cached;
64
- }
65
- const fileNames = envFilesInPriorityOrder(mode);
66
- const filePaths = fileNames.map((f) => resolve(cwd, f));
67
- const merged = {};
68
- const loadedFromFiles = [];
69
- for (const path of [...filePaths].reverse()) {
70
- const parsed = readEnvFile(path);
71
- if (parsed === null) continue;
72
- for (const [k, v] of Object.entries(parsed)) {
73
- merged[k] = v;
74
- }
75
- loadedFromFiles.unshift(path);
76
- }
77
- if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === void 0) {
78
- process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV;
79
- }
80
- delete merged.NODE_ENV;
81
- const processEnvClone = {};
82
- for (const [k, v] of Object.entries(process.env)) {
83
- if (typeof v === "string") processEnvClone[k] = v;
84
- }
85
- try {
86
- expand({ parsed: merged, processEnv: processEnvClone });
87
- } catch (err) {
88
- if (err instanceof RangeError) {
89
- console.warn(
90
- `[theokit] .env expansion overflow (likely circular reference like A=\${B}, B=\${A}). Leaving values as literals.`
91
- );
92
- } else {
93
- throw err;
94
- }
95
- }
96
- const loaded = {};
97
- for (const [k, v] of Object.entries(merged)) {
98
- if (process.env[k] !== void 0) continue;
99
- process.env[k] = v;
100
- loaded[k] = v;
101
- }
102
- process.env.__THEOKIT_PROCESSED_ENV = "true";
103
- const result = { loaded, loadedFromFiles };
104
- cache.set(cacheKey, result);
105
- return result;
106
- }
107
-
108
- // src/server/security/csrf-readiness-store.ts
109
- var CsrfReadinessStore = class _CsrfReadinessStore {
110
- static MAX_ENTRIES = 1e3;
111
- entries = /* @__PURE__ */ new Map();
112
- keyFor(event) {
113
- return `${event.method}\0${event.path}\0${event.reason}`;
114
- }
115
- record(event) {
116
- const key = this.keyFor(event);
117
- const now = (/* @__PURE__ */ new Date()).toISOString();
118
- const existing = this.entries.get(key);
119
- if (existing) {
120
- existing.count++;
121
- existing.lastSeen = now;
122
- return;
123
- }
124
- if (this.entries.size >= _CsrfReadinessStore.MAX_ENTRIES) {
125
- const firstKey = this.entries.keys().next().value;
126
- if (firstKey !== void 0) this.entries.delete(firstKey);
127
- }
128
- this.entries.set(key, { count: 1, firstSeen: now, lastSeen: now });
129
- }
130
- summary() {
131
- let total = 0;
132
- const routes = [];
133
- for (const [key, entry] of this.entries) {
134
- const [method, path, reason] = key.split("\0");
135
- routes.push({
136
- method,
137
- path,
138
- reason,
139
- count: entry.count,
140
- firstSeen: entry.firstSeen,
141
- lastSeen: entry.lastSeen
142
- });
143
- total += entry.count;
144
- }
145
- return {
146
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
147
- totalEvents: total,
148
- routes
149
- };
150
- }
151
- reset() {
152
- this.entries.clear();
153
- }
154
- };
155
-
156
- // src/core/contracts/action-protocol.ts
157
- var CODE_TO_STATUS = {
158
- VALIDATION_ERROR: 422,
159
- BAD_REQUEST: 400,
160
- UNAUTHORIZED: 401,
161
- FORBIDDEN: 403,
162
- NOT_FOUND: 404,
163
- METHOD_NOT_ALLOWED: 405,
164
- CONFLICT: 409,
165
- CONTENT_TOO_LARGE: 413,
166
- PAYLOAD_TOO_LARGE: 413,
167
- UNSUPPORTED_MEDIA_TYPE: 415,
168
- TOO_MANY_REQUESTS: 429,
169
- INTERNAL_SERVER_ERROR: 500
170
- };
171
- var STATUS_TO_CODE = {
172
- 400: "BAD_REQUEST",
173
- 401: "UNAUTHORIZED",
174
- 403: "FORBIDDEN",
175
- 404: "NOT_FOUND",
176
- 405: "METHOD_NOT_ALLOWED",
177
- 409: "CONFLICT",
178
- 413: "PAYLOAD_TOO_LARGE",
179
- 415: "UNSUPPORTED_MEDIA_TYPE",
180
- 422: "VALIDATION_ERROR",
181
- 429: "TOO_MANY_REQUESTS",
182
- 500: "INTERNAL_SERVER_ERROR"
183
- };
184
- var ActionError = class _ActionError extends Error {
185
- // Discriminator widened to the full union so subclasses can narrow to their
186
- // specific literal (TypeScript would otherwise reject the override). Concrete
187
- // values are still always exact literals at runtime.
188
- type = "TheoActionError";
189
- code;
190
- status;
191
- constructor(params) {
192
- super(params.message ?? params.code);
193
- this.code = params.code;
194
- this.status = _ActionError.codeToStatus(params.code);
195
- if (params.stack) {
196
- this.stack = params.stack;
197
- }
198
- }
199
- static codeToStatus(code) {
200
- return CODE_TO_STATUS[code];
201
- }
202
- static statusToCode(status) {
203
- return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
204
- }
205
- /**
206
- * Parse a serialized error JSON back into the typed class hierarchy.
207
- * Distinguishes `TheoActionInputError` (with `issues` array) from
208
- * `TheoActionError` via the `type` discriminator. Falls back to
209
- * `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
210
- * `type`, unknown `code`).
211
- */
212
- static fromJson(body) {
213
- if (typeof body !== "object" || body === null) {
214
- return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
215
- }
216
- const obj = body;
217
- if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
218
- return new ActionInputError(obj.issues);
219
- }
220
- if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
221
- return new _ActionError({
222
- code: obj.code,
223
- message: typeof obj.message === "string" ? obj.message : void 0
224
- });
225
- }
226
- return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
227
- }
228
- };
229
- var ActionInputError = class extends ActionError {
230
- type = "TheoActionInputError";
231
- issues;
232
- fields;
233
- constructor(rawIssues) {
234
- super({ code: "VALIDATION_ERROR", message: "Validation failed" });
235
- this.issues = extractUniversalIssues(rawIssues);
236
- this.fields = buildFieldsMap(this.issues);
237
- }
238
- };
239
- function buildFieldsMap(issues) {
240
- const fields = {};
241
- const seen = /* @__PURE__ */ new Set();
242
- for (const issue of issues) {
243
- const key = issue.path.length === 0 ? "" : issue.path.join(".");
244
- const dedupeKey = `${key}\0${issue.message}`;
245
- if (seen.has(dedupeKey)) continue;
246
- seen.add(dedupeKey);
247
- const bucket = fields[key] ?? [];
248
- bucket.push(issue.message);
249
- fields[key] = bucket;
250
- }
251
- return fields;
252
- }
253
- function extractUniversalIssues(raw) {
254
- if (!Array.isArray(raw)) return [];
255
- const out = [];
256
- for (const entry of raw) {
257
- if (typeof entry !== "object" || entry === null) continue;
258
- const obj = entry;
259
- if (!Array.isArray(obj.path)) continue;
260
- if (typeof obj.message !== "string") continue;
261
- const path = [];
262
- let pathValid = true;
263
- for (const seg of obj.path) {
264
- if (typeof seg === "string" || typeof seg === "number") {
265
- path.push(seg);
266
- } else {
267
- pathValid = false;
268
- break;
269
- }
270
- }
271
- if (!pathValid) continue;
272
- out.push({
273
- path,
274
- message: obj.message,
275
- code: typeof obj.code === "string" ? obj.code : void 0
276
- });
277
- }
278
- return out;
279
- }
280
- function isActionError(value) {
281
- return value instanceof ActionError;
282
- }
283
- function isInputError(value) {
284
- return value instanceof ActionInputError;
285
- }
286
-
287
- // src/server/body-parser.ts
288
- import { basename } from "path";
289
- import Busboy from "busboy";
290
- var METHODS_WITH_BODY = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
291
- var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
292
- var DEFAULT_MAX_FILES = 10;
293
- var DEFAULT_MAX_FIELD_SIZE = 1 * 1024 * 1024;
294
- var FileTooLargeError = class extends Error {
295
- constructor(message, truncatedFilenames, maxFileSize) {
296
- super(message);
297
- this.truncatedFilenames = truncatedFilenames;
298
- this.maxFileSize = maxFileSize;
299
- this.name = "FileTooLargeError";
300
- }
301
- truncatedFilenames;
302
- maxFileSize;
303
- code = "FILE_TOO_LARGE";
304
- status = 413;
305
- };
306
- function stashBodyPreview(req, body) {
307
- try {
308
- ;
309
- req[DEVTOOLS_BODY_PREVIEW] = body;
310
- } catch {
311
- }
312
- }
313
- function parseJsonBody(req) {
314
- return new Promise((resolve2, reject) => {
315
- const chunks = [];
316
- req.on("data", (chunk) => chunks.push(chunk));
317
- req.on("end", () => {
318
- const raw = Buffer.concat(chunks).toString();
319
- if (!raw) {
320
- resolve2(void 0);
321
- return;
322
- }
323
- try {
324
- resolve2(JSON.parse(raw));
325
- } catch {
326
- reject(new Error("Invalid JSON body"));
327
- }
328
- });
329
- req.on("error", reject);
330
- });
331
- }
332
- function parseMultipartBody(req, contentType, options) {
333
- if (!contentType.includes("boundary=")) {
334
- return Promise.reject(new Error("Missing multipart boundary"));
335
- }
336
- return new Promise((resolve2, reject) => {
337
- const fields = {};
338
- const files = [];
339
- const truncatedFilenames = [];
340
- let fileCount = 0;
341
- const bb = Busboy({
342
- headers: req.headers,
343
- limits: {
344
- fileSize: options.maxFileSize,
345
- files: options.maxFiles,
346
- fieldSize: options.maxFieldSize
347
- }
348
- });
349
- bb.on("field", (name, value) => {
350
- fields[name] = value;
351
- });
352
- bb.on(
353
- "file",
354
- (fieldname, stream, info) => {
355
- fileCount++;
356
- if (fileCount > options.maxFiles) {
357
- stream.resume();
358
- return;
359
- }
360
- const safeName = basename(info.filename || "unnamed");
361
- const chunks = [];
362
- let size = 0;
363
- let truncated = false;
364
- stream.on("data", (chunk) => {
365
- size += chunk.length;
366
- if (size > options.maxFileSize) {
367
- truncated = true;
368
- stream.resume();
369
- return;
370
- }
371
- chunks.push(chunk);
372
- });
373
- stream.on("end", () => {
374
- if (truncated) {
375
- truncatedFilenames.push(safeName);
376
- return;
377
- }
378
- const buffer = Buffer.concat(chunks);
379
- files.push({
380
- fieldname,
381
- filename: safeName,
382
- encoding: info.encoding,
383
- mimeType: info.mimeType,
384
- buffer,
385
- size: buffer.length
386
- });
387
- });
388
- }
389
- );
390
- bb.on("filesLimit", () => {
391
- reject(new Error(`Too many files. Maximum: ${options.maxFiles}`));
392
- });
393
- bb.on("error", (err) => {
394
- reject(err);
395
- });
396
- bb.on("close", () => {
397
- if (truncatedFilenames.length > 0) {
398
- reject(
399
- new FileTooLargeError(
400
- `File too large (max ${options.maxFileSize} bytes): ${truncatedFilenames.join(", ")}`,
401
- truncatedFilenames,
402
- options.maxFileSize
403
- )
404
- );
405
- return;
406
- }
407
- resolve2({ fields, files });
408
- });
409
- req.pipe(bb);
410
- });
411
- }
412
- var DEVTOOLS_BODY_PREVIEW = /* @__PURE__ */ Symbol("theo:devtools:bodyPreview");
413
- async function parseRequestBody(req, options) {
414
- const method = req.method?.toUpperCase() ?? "GET";
415
- if (!METHODS_WITH_BODY.has(method)) {
416
- return { fields: {}, files: [], json: void 0 };
417
- }
418
- const contentType = req.headers["content-type"] ?? "";
419
- if (contentType.includes("application/json")) {
420
- const json = await parseJsonBody(req);
421
- stashBodyPreview(req, json);
422
- return { fields: {}, files: [], json };
423
- }
424
- if (contentType.includes("multipart/form-data")) {
425
- const limits = {
426
- maxFileSize: options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
427
- maxFiles: options?.maxFiles ?? DEFAULT_MAX_FILES,
428
- maxFieldSize: options?.maxFieldSize ?? DEFAULT_MAX_FIELD_SIZE
429
- };
430
- const { fields, files } = await parseMultipartBody(req, contentType, limits);
431
- stashBodyPreview(req, {
432
- fields,
433
- files: files.map((f) => ({
434
- fieldname: f.fieldname,
435
- filename: f.filename,
436
- mimeType: f.mimeType,
437
- size: f.size
438
- }))
439
- });
440
- return { fields, files };
441
- }
442
- if (!contentType) {
443
- return { fields: {}, files: [] };
444
- }
445
- throw new Error(`Unsupported Content-Type: ${contentType}`);
446
- }
447
-
448
- // src/server/observability/request-log.ts
449
- var defaultLoggerFn = (log) => {
450
- console.log(JSON.stringify(log));
451
- };
452
- function logRequest(info, customLogger, req) {
453
- const log = {
454
- level: "info",
455
- ...info,
456
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
457
- };
458
- const logger = customLogger ?? defaultLoggerFn;
459
- logger(log);
460
- broadcastRequestToDevtools(info, req);
461
- }
462
- function randomRequestId() {
463
- return `req-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
464
- }
465
- function broadcastRequestToDevtools(info, req) {
466
- const headers = req?.headers ? Object.fromEntries(
467
- Object.entries(req.headers).map(([k, v]) => [
468
- k,
469
- Array.isArray(v) ? v.join(", ") : v ?? ""
470
- ])
471
- ) : void 0;
472
- const stashedBody = req ? req[DEVTOOLS_BODY_PREVIEW] : void 0;
473
- void Promise.all([
474
- import("./broadcast-4SX7R5XE.js"),
475
- import("./build-request-body-preview-EOP2Y4F3.js")
476
- ]).then(([{ broadcastRequest }, { buildRequestBodyPreview }]) => {
477
- const bodyInfo = buildRequestBodyPreview(stashedBody);
478
- broadcastRequest({
479
- id: randomRequestId(),
480
- traceId: info.requestId,
481
- method: info.method,
482
- path: info.url,
483
- status: info.status,
484
- durationMs: info.duration,
485
- startedAt: Date.now() - info.duration,
486
- ...headers ? { headers } : {},
487
- ...bodyInfo ? {
488
- bodyPreview: bodyInfo.preview,
489
- bodyLength: bodyInfo.length,
490
- bodyTruncated: bodyInfo.truncated
491
- } : {}
492
- });
493
- }).catch(() => {
494
- });
495
- }
496
-
497
- // src/server/observability/logger.ts
498
- var LEVEL_ORDER = {
499
- debug: 0,
500
- info: 1,
501
- warn: 2,
502
- error: 3,
503
- silent: 4
504
- };
505
- function shouldLog(msgLevel, configLevel) {
506
- return LEVEL_ORDER[msgLevel] >= LEVEL_ORDER[configLevel];
507
- }
508
- function defaultOutput(log) {
509
- console.log(JSON.stringify(log));
510
- }
511
- function createLogger(options) {
512
- const level = options?.level ?? "info";
513
- const output = options?.output ?? defaultOutput;
514
- const baseContext = options?.context ?? {};
515
- function log(msgLevel, msg, context) {
516
- if (!shouldLog(msgLevel, level)) return;
517
- const entry = {
518
- level: msgLevel,
519
- msg,
520
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
521
- ...baseContext,
522
- ...context
523
- };
524
- if (entry.error instanceof Error) {
525
- entry.error = entry.error.stack ?? entry.error.message;
526
- }
527
- output(entry);
528
- }
529
- return {
530
- debug: (msg, ctx) => {
531
- log("debug", msg, ctx);
532
- },
533
- info: (msg, ctx) => {
534
- log("info", msg, ctx);
535
- },
536
- warn: (msg, ctx) => {
537
- log("warn", msg, ctx);
538
- },
539
- error: (msg, ctx) => {
540
- log("error", msg, ctx);
541
- },
542
- child(ctx) {
543
- return createLogger({
544
- level,
545
- output,
546
- context: { ...baseContext, ...ctx }
547
- });
548
- }
549
- };
550
- }
551
- var _warnOnceSeen = /* @__PURE__ */ new Set();
552
- var MAX_SEEN = 1024;
553
- function _resetWarnOnceForTests() {
554
- _warnOnceSeen.clear();
555
- }
556
- function warnOnce(key, payload) {
557
- if (_warnOnceSeen.has(key)) return;
558
- if (_warnOnceSeen.size >= MAX_SEEN) {
559
- const oldest = _warnOnceSeen.values().next().value;
560
- if (oldest !== void 0) _warnOnceSeen.delete(oldest);
561
- }
562
- _warnOnceSeen.add(key);
563
- let line;
564
- try {
565
- line = JSON.stringify({ ...payload, warnOnce: true });
566
- } catch {
567
- line = `[warnOnce] ${key} \u2014 (payload could not be serialized)`;
568
- }
569
- console.warn(line);
570
- broadcastWarnOnceToDevtools(key, payload);
571
- }
572
- function payloadField(payload, key, fallback = "") {
573
- const value = payload[key];
574
- if (typeof value === "string") return value;
575
- if (typeof value === "number" || typeof value === "boolean") return String(value);
576
- return fallback;
577
- }
578
- function randomDevtoolsId() {
579
- return `warn-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
580
- }
581
- function broadcastWarnOnceToDevtools(key, payload) {
582
- void import("./broadcast-4SX7R5XE.js").then(({ broadcastCsrfWarn, broadcastError }) => {
583
- if (payload.event === "csrf.warn" && typeof payload.code === "string" && typeof payload.docsUrl === "string") {
584
- broadcastCsrfWarn({
585
- event: "csrf.warn",
586
- code: payload.code,
587
- docsUrl: payload.docsUrl,
588
- method: payloadField(payload, "method"),
589
- path: payloadField(payload, "path"),
590
- reason: payloadField(payload, "reason")
591
- });
592
- } else {
593
- broadcastError({
594
- id: randomDevtoolsId(),
595
- type: "console",
596
- message: payloadField(payload, "event", key),
597
- timestamp: Date.now()
598
- });
599
- }
600
- }).catch(() => {
601
- });
602
- }
603
-
604
- // src/server/observability/audit-log.ts
605
- var JsonStdoutSink = class {
606
- log(event) {
607
- const enriched = {
608
- level: "audit",
609
- ...event,
610
- timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
611
- };
612
- try {
613
- console.log(JSON.stringify(enriched, jsonReplacer));
614
- } catch {
615
- console.log(
616
- `{"level":"audit","action":${JSON.stringify(event.action)},"timestamp":${JSON.stringify(enriched.timestamp)},"note":"payload could not be serialized"}`
617
- );
618
- }
619
- }
620
- };
621
- function jsonReplacer(_key, value) {
622
- if (typeof value === "bigint") return value.toString();
623
- return value;
624
- }
625
- function createNoOpLogger() {
626
- return {
627
- log() {
628
- }
629
- };
630
- }
631
- function safeAudit(logger, event) {
632
- if (!logger) return;
633
- try {
634
- const r = logger.log(event);
635
- if (r && typeof r.then === "function") {
636
- r.catch(() => {
637
- });
638
- }
639
- } catch {
640
- }
641
- }
642
-
643
- // src/server/security/csrf.ts
644
- function matchDisallowed(path, patterns) {
645
- for (const p of patterns) {
646
- if (typeof p === "string") {
647
- if (path === p) return true;
648
- } else if (p instanceof RegExp) {
649
- p.lastIndex = 0;
650
- if (p.test(path)) return true;
651
- }
652
- }
653
- return false;
654
- }
655
- var CSRF_WARN_CODE = "CSRF_STRICT_CUTOVER";
656
- var CSRF_WARN_DOCS_URL = "https://theokit.dev/upgrade/csrf-strict-cutover";
657
- function pickHeader(value) {
658
- if (typeof value === "string") return value;
659
- if (Array.isArray(value) && value.length > 0) return value[0];
660
- return "";
661
- }
662
- function validateCsrf(req) {
663
- if (req.headers["x-theo-action"] !== "1") {
664
- return { valid: false, reason: "Missing X-Theo-Action header" };
665
- }
666
- const origin = req.headers.origin;
667
- if (!origin) {
668
- return { valid: true };
669
- }
670
- const host = req.headers.host;
671
- if (!host) {
672
- return { valid: true };
673
- }
674
- const originStr = pickHeader(origin);
675
- const hostStr = pickHeader(host);
676
- if (originStr === "" || hostStr === "") return { valid: true };
677
- try {
678
- const originHost = new URL(originStr).host;
679
- if (originHost !== hostStr) {
680
- return { valid: false, reason: `Origin ${originStr} does not match host ${hostStr}` };
681
- }
682
- } catch {
683
- return { valid: false, reason: `Invalid origin: ${originStr}` };
684
- }
685
- return { valid: true };
686
- }
687
- function dispatchCsrfWarn(req, reason, logger, auditLogger, pathFallback = "") {
688
- const payload = {
689
- event: "csrf.warn",
690
- method: req.method ?? "UNKNOWN",
691
- path: logger?.path ?? pathFallback,
692
- reason,
693
- code: CSRF_WARN_CODE,
694
- docsUrl: CSRF_WARN_DOCS_URL
695
- };
696
- logger?.warn(payload);
697
- safeAudit(auditLogger, {
698
- action: "csrf.warn",
699
- actor: { type: "anonymous" },
700
- metadata: { ...payload }
701
- });
702
- }
703
- function enforceCsrf(req, mode, logger, disallowed, auditLogger) {
704
- if (mode === "off") {
705
- return { allow: true };
706
- }
707
- const check = validateCsrf(req);
708
- if (check.valid) {
709
- return { allow: true };
710
- }
711
- if (disallowed?.behavior === "raise") {
712
- const path = logger?.path ?? req.url ?? "";
713
- if (matchDisallowed(path, disallowed.routes)) {
714
- return { allow: false, reason: check.reason };
715
- }
716
- }
717
- if (mode === "warn") {
718
- dispatchCsrfWarn(req, check.reason, logger, auditLogger);
719
- return { allow: true, reason: check.reason };
720
- }
721
- dispatchCsrfWarn(req, check.reason, logger, auditLogger);
722
- return { allow: false, reason: check.reason };
723
- }
724
-
725
- // src/server/http/send-response.ts
726
- function sendJson(res, data, status = 200, transformer) {
727
- const body = transformer ? transformer.serialize(data) : JSON.stringify(data);
728
- res.writeHead(status, {
729
- "Content-Type": "application/json",
730
- "Content-Length": Buffer.byteLength(body)
731
- });
732
- res.end(body);
733
- }
734
- function sendError(res, codeOrInput, message, status, issues, requestId, options) {
735
- let code;
736
- if (typeof codeOrInput === "string") {
737
- code = codeOrInput;
738
- message = message ?? "";
739
- status = status ?? 500;
740
- } else {
741
- code = codeOrInput.code;
742
- message = codeOrInput.message;
743
- status = codeOrInput.status;
744
- issues = codeOrInput.issues;
745
- requestId = codeOrInput.requestId;
746
- options = codeOrInput.options;
747
- }
748
- const errorMessage = code === "INTERNAL_ERROR" && process.env.NODE_ENV === "production" ? "Internal server error" : message;
749
- if (code === "INTERNAL_ERROR") {
750
- console.error(`[${requestId ?? "no-id"}] ${message}`);
751
- }
752
- if (status === 404 && options?.custom404Html) {
753
- const body = options.custom404Html;
754
- res.writeHead(404, {
755
- "Content-Type": "text/html; charset=utf-8",
756
- "Content-Length": Buffer.byteLength(body)
757
- });
758
- res.end(body);
759
- return;
760
- }
761
- if (status === 500 && options?.custom500Html) {
762
- const body = options.custom500Html;
763
- res.writeHead(500, {
764
- "Content-Type": "text/html; charset=utf-8",
765
- "Content-Length": Buffer.byteLength(body)
766
- });
767
- res.end(body);
768
- return;
769
- }
770
- sendJson(
771
- res,
772
- {
773
- error: {
774
- code,
775
- message: errorMessage,
776
- ...requestId ? { requestId } : {},
777
- ...issues ? { issues } : {}
778
- }
779
- },
780
- status
781
- );
782
- }
783
-
784
- // src/server/scan/middleware-scan.ts
785
- import { readdirSync, existsSync, statSync as statSync2 } from "fs";
786
- import { join, extname } from "path";
787
- var MW_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
788
- function scanMiddlewares(serverDir) {
789
- const mwDir = join(serverDir, "middleware");
790
- if (!existsSync(mwDir) || !statSync2(mwDir).isDirectory()) {
791
- return [];
792
- }
793
- const entries = readdirSync(mwDir, { withFileTypes: true });
794
- const files = [];
795
- for (const entry of entries) {
796
- if (!entry.isFile()) continue;
797
- if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
798
- const ext = extname(entry.name);
799
- if (!MW_EXTENSIONS.has(ext)) continue;
800
- files.push(join(mwDir, entry.name));
801
- }
802
- files.sort((a, b) => a.localeCompare(b));
803
- return files;
804
- }
805
-
806
- // src/server/http/middleware-runner.ts
807
- import { existsSync as existsSync2 } from "fs";
808
- import { join as join2 } from "path";
809
- var middlewareCache = /* @__PURE__ */ new Map();
810
- function _resetMiddlewareCacheForTests() {
811
- middlewareCache.clear();
812
- }
813
- function getCachedScan(serverDir) {
814
- let cached = middlewareCache.get(serverDir);
815
- if (!cached) {
816
- const singleFilePath = join2(serverDir, "middleware.ts");
817
- cached = {
818
- singleFilePath,
819
- singleFileExists: existsSync2(singleFilePath),
820
- dirMiddlewares: scanMiddlewares(serverDir)
821
- };
822
- middlewareCache.set(serverDir, cached);
823
- }
824
- return cached;
825
- }
826
- async function runOneMiddleware(mw, req, res) {
827
- const state = { nextCalled: false };
828
- await mw(req, res, () => {
829
- state.nextCalled = true;
830
- });
831
- return state;
832
- }
833
- async function runMiddlewareAndContext(req, res, loadModule, serverDir) {
834
- const { singleFilePath, singleFileExists, dirMiddlewares } = getCachedScan(serverDir);
835
- const dirExists = dirMiddlewares.length > 0;
836
- if (singleFileExists && dirExists) {
837
- throw new Error(
838
- "Ambiguous middleware configuration: found both server/middleware.ts and server/middleware/ directory. Use one or the other, not both."
839
- );
840
- }
841
- if (dirExists) {
842
- for (const mwPath of dirMiddlewares) {
843
- const mod = await loadModule(mwPath);
844
- const mw = mod.default;
845
- if (typeof mw !== "function") continue;
846
- const { nextCalled } = await runOneMiddleware(mw, req, res);
847
- if (!nextCalled || res.writableEnded) {
848
- return { ctx: {}, aborted: true };
849
- }
850
- }
851
- }
852
- if (singleFileExists) {
853
- const mod = await loadModule(singleFilePath);
854
- const mw = mod.default;
855
- if (typeof mw === "function") {
856
- const { nextCalled } = await runOneMiddleware(mw, req, res);
857
- if (!nextCalled || res.writableEnded) {
858
- return { ctx: {}, aborted: true };
859
- }
860
- }
861
- }
862
- let ctx = {};
863
- const contextPath = join2(serverDir, "context.ts");
864
- if (existsSync2(contextPath)) {
865
- const mod = await loadModule(contextPath);
866
- const createContext = mod.createContext;
867
- if (typeof createContext === "function") {
868
- ctx = await createContext({ request: req, response: res });
869
- }
870
- }
871
- return { ctx, aborted: false };
872
- }
873
-
874
- // src/server/security/csrf-warn-dispatch.ts
875
- function dispatchCsrfWarn2(payload) {
876
- const key = `${payload.event}:${payload.method}:${payload.path ?? ""}`;
877
- warnOnce(key, payload);
878
- }
879
-
880
- // src/server/http/execute-stages.ts
881
- async function parseQueryAndBody(req, res, requestId) {
882
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
883
- const query = Object.fromEntries(url.searchParams);
884
- let body;
885
- try {
886
- const parsed = await parseRequestBody(req);
887
- if (parsed.json !== void 0) {
888
- body = parsed.json;
889
- } else if (parsed.files.length > 0 || Object.keys(parsed.fields).length > 0) {
890
- body = { ...parsed.fields, _files: parsed.files };
891
- } else {
892
- body = void 0;
893
- }
894
- } catch (err) {
895
- const message = err.message;
896
- const status = message.includes("Unsupported Content-Type") ? 415 : 400;
897
- sendError(res, "VALIDATION_ERROR", message, status, void 0, requestId);
898
- return { ok: false };
899
- }
900
- return { ok: true, data: { query, body } };
901
- }
902
- var isZodLike = (value) => typeof value === "object" && value !== null && typeof value.safeParse === "function";
903
- function runZodValidation(routeConfig, res, requestId, input) {
904
- const { query, params } = input;
905
- let { body } = input;
906
- if (isZodLike(routeConfig.query)) {
907
- const result = routeConfig.query.safeParse(query);
908
- if (!result.success) {
909
- sendError(
910
- res,
911
- "VALIDATION_ERROR",
912
- "Invalid query parameters",
913
- 400,
914
- result.error?.issues,
915
- requestId
916
- );
917
- return { ok: false };
918
- }
919
- Object.assign(query, result.data);
920
- }
921
- if (isZodLike(routeConfig.body)) {
922
- const result = routeConfig.body.safeParse(body);
923
- if (!result.success) {
924
- sendError(
925
- res,
926
- "VALIDATION_ERROR",
927
- "Invalid request body",
928
- 400,
929
- result.error?.issues,
930
- requestId
931
- );
932
- return { ok: false };
933
- }
934
- body = result.data;
935
- }
936
- if (isZodLike(routeConfig.params)) {
937
- const result = routeConfig.params.safeParse(params);
938
- if (!result.success) {
939
- sendError(
940
- res,
941
- "VALIDATION_ERROR",
942
- "Invalid route parameters",
943
- 400,
944
- result.error?.issues,
945
- requestId
946
- );
947
- return { ok: false };
948
- }
949
- Object.assign(params, result.data);
950
- }
951
- return { ok: true, data: { query, body, params } };
952
- }
953
-
954
- // src/server/http/execute.ts
955
- var CSRF_PROTECTED_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
956
- async function pipeWebStreamToResponse(body, res, ctx) {
957
- const reader = body.getReader();
958
- try {
959
- let done = false;
960
- while (!done) {
961
- const chunk = await reader.read();
962
- done = chunk.done;
963
- if (!done) res.write(chunk.value);
964
- }
965
- } catch (streamErr) {
966
- warnOnce(`stream-error:${ctx.routePath}:${ctx.method}`, {
967
- event: "stream.error",
968
- requestId: ctx.requestId ?? "no-id",
969
- route: ctx.routePath,
970
- method: ctx.method,
971
- message: streamErr instanceof Error ? streamErr.message : String(streamErr)
972
- });
973
- if (ctx.pluginRunner) {
974
- try {
975
- await ctx.pluginRunner.runOnError(ctx.buildPluginCtx(ctx.ctx), streamErr);
976
- } catch {
977
- }
978
- }
979
- } finally {
980
- try {
981
- reader.releaseLock();
982
- } catch {
983
- }
984
- }
985
- }
986
- async function executeRoute(ctx) {
987
- const {
988
- route,
989
- method,
990
- params,
991
- req,
992
- res,
993
- loadModule,
994
- serverDir,
995
- requestId,
996
- pluginRunner,
997
- transformer,
998
- csrfMode = "strict",
999
- disallowed,
1000
- jobBackend
1001
- } = ctx;
1002
- const buildPluginCtx = (ctxObj) => ({
1003
- request: req,
1004
- response: res,
1005
- ctx: ctxObj,
1006
- requestId: requestId ?? "no-id"
1007
- });
1008
- if (transformer && transformer.name !== "json") {
1009
- res.setHeader("x-theo-transformer", transformer.name);
1010
- }
1011
- try {
1012
- let ctx2 = {};
1013
- if (pluginRunner) {
1014
- pluginRunner.applyDecorations(ctx2);
1015
- const onReqResult = await pluginRunner.runOnRequest(buildPluginCtx(ctx2));
1016
- if (onReqResult.shortCircuited) return;
1017
- }
1018
- if (serverDir) {
1019
- const result = await runMiddlewareAndContext(req, res, loadModule, serverDir);
1020
- if (result.aborted) return;
1021
- ctx2 = result.ctx ?? {};
1022
- if (pluginRunner) pluginRunner.applyDecorations(ctx2);
1023
- }
1024
- if (jobBackend) {
1025
- if (ctx2.queue !== void 0) {
1026
- throw new DuplicateContextKeyError("queue", {
1027
- reason: "A plugin or middleware already decorated ctx.queue; choose a different key OR remove jobs.backend from theo.config.ts."
1028
- });
1029
- }
1030
- const outbox = createOutbox();
1031
- const queueClient = createQueueClient(jobBackend, outbox);
1032
- ctx2.queue = queueClient;
1033
- res.on("close", () => {
1034
- if (!res.writableFinished) outbox.discard();
1035
- });
1036
- res.on("finish", () => {
1037
- if (res.statusCode >= 400) {
1038
- outbox.discard();
1039
- return;
1040
- }
1041
- void outbox.flush(createOutboxDispatcher(jobBackend));
1042
- });
1043
- }
1044
- const mod = await loadModule(route.filePath);
1045
- const routeConfig = mod[method];
1046
- if (!routeConfig) {
1047
- sendError(
1048
- res,
1049
- "METHOD_NOT_ALLOWED",
1050
- `Method ${method} not allowed`,
1051
- 405,
1052
- void 0,
1053
- requestId
1054
- );
1055
- return;
1056
- }
1057
- const handler = typeof routeConfig === "function" ? routeConfig : routeConfig.handler;
1058
- if (typeof handler !== "function") {
1059
- sendError(res, "INTERNAL_ERROR", "Route handler is not a function", 500, void 0, requestId);
1060
- return;
1061
- }
1062
- const routeOptOut = typeof routeConfig === "object" && routeConfig.csrf === false;
1063
- if (CSRF_PROTECTED_METHODS.has(method) && !routeOptOut) {
1064
- const decision = enforceCsrf(
1065
- req,
1066
- csrfMode,
1067
- {
1068
- // T3.3 DRY — see security/csrf-warn-dispatch.ts
1069
- warn: dispatchCsrfWarn2,
1070
- path: req.url
1071
- },
1072
- disallowed
1073
- );
1074
- if (!decision.allow) {
1075
- sendError(
1076
- res,
1077
- "CSRF_INVALID",
1078
- decision.reason ?? "CSRF check failed",
1079
- 403,
1080
- void 0,
1081
- requestId
1082
- );
1083
- return;
1084
- }
1085
- }
1086
- const rc = routeConfig;
1087
- const parseResult = await parseQueryAndBody(req, res, requestId);
1088
- if (!parseResult.ok) return;
1089
- const { query } = parseResult.data;
1090
- let { body } = parseResult.data;
1091
- const validationResult = runZodValidation(rc, res, requestId, { query, body, params });
1092
- if (!validationResult.ok) return;
1093
- body = validationResult.data.body;
1094
- if (pluginRunner) {
1095
- const preResult = await pluginRunner.runPreHandler(buildPluginCtx(ctx2));
1096
- if (preResult.shortCircuited) return;
1097
- }
1098
- const callableHandler = handler;
1099
- const handlerResult = await callableHandler({ query, body, params, request: req, ctx: ctx2 });
1100
- if (handlerResult === void 0 || handlerResult === null) {
1101
- sendJson(res, null, rc.status ?? 204, transformer);
1102
- if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
1103
- return;
1104
- }
1105
- if (handlerResult instanceof Response) {
1106
- const headersBag = {};
1107
- for (const [k, v] of handlerResult.headers) {
1108
- if (k.toLowerCase() !== "set-cookie") headersBag[k] = v;
1109
- }
1110
- const setCookies = handlerResult.headers.getSetCookie();
1111
- if (setCookies.length > 0) {
1112
- res.setHeader("Set-Cookie", setCookies);
1113
- }
1114
- res.writeHead(handlerResult.status, headersBag);
1115
- if (handlerResult.body) {
1116
- await pipeWebStreamToResponse(handlerResult.body, res, {
1117
- buildPluginCtx,
1118
- ctx: ctx2,
1119
- method,
1120
- pluginRunner,
1121
- requestId,
1122
- routePath: route.routePath
1123
- });
1124
- }
1125
- res.end();
1126
- if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
1127
- return;
1128
- }
1129
- sendJson(res, handlerResult, rc.status ?? 200, transformer);
1130
- if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
1131
- } catch (err) {
1132
- if (pluginRunner) {
1133
- const errCtxObj = {};
1134
- pluginRunner.applyDecorations(errCtxObj);
1135
- await pluginRunner.runOnError(buildPluginCtx(errCtxObj), err);
1136
- if (res.writableEnded) {
1137
- await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
1138
- return;
1139
- }
1140
- }
1141
- const isAuthError = err instanceof AuthRequiredError || err !== null && typeof err === "object" && err.code === "AUTH_REQUIRED" && err.status === 401;
1142
- if (isAuthError) {
1143
- const authErr = err;
1144
- sendError(res, authErr.code, authErr.message, authErr.status, void 0, requestId);
1145
- if (pluginRunner) {
1146
- const errCtxObj = {};
1147
- pluginRunner.applyDecorations(errCtxObj);
1148
- await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
1149
- }
1150
- return;
1151
- }
1152
- sendError(
1153
- res,
1154
- "INTERNAL_ERROR",
1155
- err instanceof Error && err.message ? err.message : "Internal server error",
1156
- 500,
1157
- void 0,
1158
- requestId
1159
- );
1160
- if (pluginRunner) {
1161
- const errCtxObj = {};
1162
- pluginRunner.applyDecorations(errCtxObj);
1163
- await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
1164
- }
1165
- }
1166
- }
1167
-
1168
- // src/server/http/form-data-to-object.ts
1169
- import { z } from "zod";
1170
- function formDataToObject(formData, schema, prefix = "") {
1171
- const shape = schema._def.shape();
1172
- const out = {};
1173
- for (const [key, rawValidator] of Object.entries(shape)) {
1174
- const fullKey = prefix + key;
1175
- const validator = unwrapWrappers(rawValidator);
1176
- if (validator instanceof z.ZodObject) {
1177
- const nestedPrefix = `${fullKey}.`;
1178
- const hasNestedKeys = [...formData.keys()].some((k) => k.startsWith(nestedPrefix));
1179
- if (hasNestedKeys) {
1180
- out[key] = formDataToObject(formData, validator, nestedPrefix);
1181
- continue;
1182
- }
1183
- out[key] = unwrapMissingDefault(rawValidator);
1184
- continue;
1185
- }
1186
- if (validator instanceof z.ZodArray) {
1187
- const values = formData.getAll(fullKey);
1188
- out[key] = coerceArrayElements(values, validator);
1189
- continue;
1190
- }
1191
- if (validator instanceof z.ZodBoolean) {
1192
- out[key] = coerceBoolean(formData, fullKey);
1193
- continue;
1194
- }
1195
- if (formData.has(fullKey)) {
1196
- const raw = formData.get(fullKey);
1197
- out[key] = coerceScalar(raw, validator);
1198
- } else {
1199
- out[key] = unwrapMissingDefault(rawValidator);
1200
- }
1201
- }
1202
- return out;
1203
- }
1204
- function unwrapWrappers(validator) {
1205
- let inner = validator;
1206
- while (inner instanceof z.ZodOptional || inner instanceof z.ZodNullable || inner instanceof z.ZodDefault) {
1207
- inner = inner._def.innerType;
1208
- }
1209
- return inner;
1210
- }
1211
- function unwrapMissingDefault(validator) {
1212
- let cursor = validator;
1213
- while (cursor instanceof z.ZodOptional || cursor instanceof z.ZodNullable || cursor instanceof z.ZodDefault) {
1214
- if (cursor instanceof z.ZodDefault) {
1215
- const def = cursor._def.defaultValue;
1216
- return typeof def === "function" ? def() : def;
1217
- }
1218
- if (cursor instanceof z.ZodNullable) return null;
1219
- cursor = cursor._def.innerType;
1220
- }
1221
- return void 0;
1222
- }
1223
- function coerceScalar(raw, validator) {
1224
- if (raw === null) return void 0;
1225
- if (validator instanceof z.ZodNumber) {
1226
- return typeof raw === "string" ? Number(raw) : raw;
1227
- }
1228
- return raw;
1229
- }
1230
- function coerceArrayElements(values, arrayValidator) {
1231
- const elementType = unwrapWrappers(arrayValidator._def.type);
1232
- if (elementType instanceof z.ZodNumber) {
1233
- return values.map((v) => typeof v === "string" ? Number(v) : v);
1234
- }
1235
- if (elementType instanceof z.ZodBoolean) {
1236
- return values.map((v) => {
1237
- if (v === "true") return true;
1238
- if (v === "false") return false;
1239
- return Boolean(v);
1240
- });
1241
- }
1242
- return values;
1243
- }
1244
- function coerceBoolean(formData, key) {
1245
- if (!formData.has(key)) return void 0;
1246
- const val = formData.get(key);
1247
- if (val === "true") return true;
1248
- if (val === "false") return false;
1249
- return Boolean(val);
1250
- }
1251
-
1252
- // src/server/http/handle-request-error.ts
1253
- async function handleRequestError(err, c) {
1254
- if (c.pluginRunner) {
1255
- const errCtxObj = {};
1256
- c.pluginRunner.applyDecorations(errCtxObj);
1257
- try {
1258
- await c.pluginRunner.runOnError(c.buildPluginCtx(errCtxObj), err);
1259
- } catch {
1260
- }
1261
- if (c.res.writableEnded) {
1262
- try {
1263
- await c.pluginRunner.runOnResponse(c.buildPluginCtx(errCtxObj), {
1264
- inErrorPath: true
1265
- });
1266
- } catch {
1267
- }
1268
- return;
1269
- }
1270
- }
1271
- const isAuthError = err instanceof AuthRequiredError || err !== null && typeof err === "object" && err.code === "AUTH_REQUIRED" && err.status === 401;
1272
- if (isAuthError) {
1273
- const authErr = err;
1274
- sendError(c.res, authErr.code, authErr.message, authErr.status, void 0, c.requestId);
1275
- } else {
1276
- sendError(
1277
- c.res,
1278
- "INTERNAL_ERROR",
1279
- err instanceof Error ? err.message : "Internal server error",
1280
- 500,
1281
- void 0,
1282
- c.requestId
1283
- );
1284
- }
1285
- if (c.pluginRunner) {
1286
- const errCtxObj = {};
1287
- c.pluginRunner.applyDecorations(errCtxObj);
1288
- try {
1289
- await c.pluginRunner.runOnResponse(c.buildPluginCtx(errCtxObj), {
1290
- inErrorPath: true
1291
- });
1292
- } catch {
1293
- }
1294
- }
1295
- }
1296
-
1297
- // src/server/http/serialize-action-result.ts
1298
- import { stringify as devalueStringify } from "devalue";
1299
- var DEFAULT_RESPONSE_BODY_SIZE_LIMIT = 5 * 1024 * 1024;
1300
- function serializeActionResult(result, options = {}) {
1301
- const limit = options.responseBodySizeLimit ?? DEFAULT_RESPONSE_BODY_SIZE_LIMIT;
1302
- if (result.error !== void 0) {
1303
- const body2 = result.error instanceof ActionInputError ? JSON.stringify({
1304
- type: result.error.type,
1305
- code: result.error.code,
1306
- message: result.error.message,
1307
- issues: result.error.issues,
1308
- fields: result.error.fields
1309
- }) : JSON.stringify({
1310
- type: result.error.type,
1311
- code: result.error.code,
1312
- message: result.error.message
1313
- });
1314
- if (Buffer.byteLength(body2, "utf8") > limit) {
1315
- throw new ActionError({
1316
- code: "PAYLOAD_TOO_LARGE",
1317
- message: `Serialized error body exceeds limit (${limit} bytes)`
1318
- });
1319
- }
1320
- return {
1321
- type: "error",
1322
- status: result.error.status,
1323
- contentType: "application/json",
1324
- body: body2
1325
- };
1326
- }
1327
- if (result.data === void 0) {
1328
- return { type: "empty", status: 204 };
1329
- }
1330
- if (result.data instanceof Response) {
1331
- throw new ActionError({
1332
- code: "INTERNAL_SERVER_ERROR",
1333
- message: "Action handler cannot serialize Response objects \u2014 return plain data instead"
1334
- });
1335
- }
1336
- let body;
1337
- try {
1338
- body = devalueStringify(result.data, {
1339
- URL: (value) => value instanceof URL && value.href
1340
- });
1341
- } catch (e) {
1342
- throw new ActionError({
1343
- code: "INTERNAL_SERVER_ERROR",
1344
- message: `Action data not serializable: ${e instanceof Error ? e.message : String(e)}`
1345
- });
1346
- }
1347
- if (Buffer.byteLength(body, "utf8") > limit) {
1348
- throw new ActionError({
1349
- code: "PAYLOAD_TOO_LARGE",
1350
- message: `Serialized response body exceeds limit (${limit} bytes)`
1351
- });
1352
- }
1353
- return {
1354
- type: "data",
1355
- status: 200,
1356
- contentType: "application/json+devalue",
1357
- body
1358
- };
1359
- }
1360
-
1361
- // src/server/http/action-execute.ts
1362
- var __IS_DEV = (() => {
1363
- try {
1364
- return import.meta.env?.DEV === true;
1365
- } catch {
1366
- return process.env.NODE_ENV !== "production";
1367
- }
1368
- })();
1369
- function isActionConfig(value) {
1370
- if (typeof value !== "object" || value === null) return false;
1371
- const candidate = value;
1372
- if (typeof candidate.handler !== "function") return false;
1373
- const input = candidate.input;
1374
- return typeof input?.safeParse === "function";
1375
- }
1376
- async function executeAction(filePath, exportName, req, res, loadModule, serverDir, requestId, pluginRunner, csrfMode = "strict", disallowed) {
1377
- return executeActionWithOptions({
1378
- filePath,
1379
- exportName,
1380
- req,
1381
- res,
1382
- loadModule,
1383
- serverDir,
1384
- requestId,
1385
- pluginRunner,
1386
- csrfMode,
1387
- disallowed
1388
- });
1389
- }
1390
- async function loadActionConfig(loadModule, filePath, exportName, res, requestId) {
1391
- const mod = await loadModule(filePath);
1392
- const exportedValue = mod[exportName];
1393
- if (!isActionConfig(exportedValue)) {
1394
- sendError(res, "NOT_FOUND", `Action "${exportName}" not found`, 404, void 0, requestId);
1395
- return null;
1396
- }
1397
- return exportedValue;
1398
- }
1399
- function enforceCsrfForAction(req, res, ctx) {
1400
- if (ctx.actionConfig.csrf === false) return true;
1401
- const decision = enforceCsrf(
1402
- req,
1403
- ctx.csrfMode,
1404
- {
1405
- // T3.3 DRY — canonical dispatcher
1406
- warn: dispatchCsrfWarn2,
1407
- path: req.url
1408
- },
1409
- ctx.disallowed
1410
- );
1411
- if (decision.allow) return true;
1412
- sendError(
1413
- res,
1414
- "CSRF_INVALID",
1415
- decision.reason ?? "CSRF check failed",
1416
- 403,
1417
- void 0,
1418
- ctx.requestId
1419
- );
1420
- return false;
1421
- }
1422
- async function runPreHandlerPipeline(p) {
1423
- if (p.serverDir) {
1424
- const result = await runMiddlewareAndContext(p.req, p.res, p.loadModule, p.serverDir);
1425
- if (result.aborted) return false;
1426
- Object.assign(p.ctx, result.ctx ?? {});
1427
- p.pluginRunner?.applyDecorations(p.ctx);
1428
- }
1429
- if (p.pluginRunner) {
1430
- const preResult = await p.pluginRunner.runPreHandler(p.buildPluginCtx(p.ctx));
1431
- if (preResult.shortCircuited) return false;
1432
- }
1433
- return true;
1434
- }
1435
- async function readActionBody(req, res, requestId, actionConfig) {
1436
- try {
1437
- const parsed = await parseRequestBody(req);
1438
- const accept = actionConfig.accept ?? "json";
1439
- let body;
1440
- if (accept === "form") {
1441
- body = formDataToObject(
1442
- synthesizeFormData(parsed),
1443
- actionConfig.input
1444
- );
1445
- } else if (parsed.json !== void 0) {
1446
- body = parsed.json;
1447
- } else {
1448
- body = parsed.fields;
1449
- }
1450
- return { ok: true, body };
1451
- } catch (err) {
1452
- sendError(res, "VALIDATION_ERROR", err.message, 400, void 0, requestId);
1453
- return { ok: false };
1454
- }
1455
- }
1456
- function synthesizeFormData(parsed) {
1457
- const fd = new FormData();
1458
- for (const [name, value] of Object.entries(parsed.fields)) {
1459
- fd.append(name, value);
1460
- }
1461
- for (const file of parsed.files) {
1462
- const blob = new Blob([file.buffer], {
1463
- type: file.mimeType
1464
- });
1465
- fd.append(file.fieldname, blob, file.filename);
1466
- }
1467
- return fd;
1468
- }
1469
- function writeSerialized(res, serialized) {
1470
- if (serialized.type === "empty") {
1471
- res.statusCode = serialized.status;
1472
- res.end();
1473
- return;
1474
- }
1475
- res.statusCode = serialized.status;
1476
- res.setHeader("Content-Type", serialized.contentType);
1477
- res.end(serialized.body);
1478
- }
1479
- async function emitActionCallTelemetry(name, startedAt, input, outcome) {
1480
- if (!__IS_DEV) return;
1481
- try {
1482
- const mod = await import("./dispatcher-AQJGI3HU.js");
1483
- mod.dispatcher.onActionCall({
1484
- // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id
1485
- id: `act-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`,
1486
- timestamp: startedAt,
1487
- name,
1488
- input,
1489
- output: outcome.status === "success" ? outcome.output : void 0,
1490
- error: outcome.status === "error" ? {
1491
- code: outcome.error.code,
1492
- message: outcome.error.message,
1493
- fields: outcome.error instanceof ActionInputError ? outcome.error.fields : void 0
1494
- } : void 0,
1495
- durationMs: Date.now() - startedAt,
1496
- status: outcome.status
1497
- });
1498
- } catch {
1499
- }
1500
- }
1501
- async function executeActionWithOptions(opts) {
1502
- const {
1503
- filePath,
1504
- exportName,
1505
- req,
1506
- res,
1507
- loadModule,
1508
- serverDir,
1509
- requestId,
1510
- pluginRunner,
1511
- csrfMode = "strict",
1512
- disallowed
1513
- } = opts;
1514
- const buildPluginCtx = (ctxObj) => ({
1515
- request: req,
1516
- response: res,
1517
- ctx: ctxObj,
1518
- requestId: requestId ?? "no-id"
1519
- });
1520
- let ctx = {};
1521
- try {
1522
- if ((req.method ?? "GET").toUpperCase() !== "POST") {
1523
- sendError(res, "METHOD_NOT_ALLOWED", "Actions only accept POST", 405, void 0, requestId);
1524
- return;
1525
- }
1526
- if (pluginRunner) {
1527
- pluginRunner.applyDecorations(ctx);
1528
- const onReqResult = await pluginRunner.runOnRequest(buildPluginCtx(ctx));
1529
- if (onReqResult.shortCircuited) return;
1530
- }
1531
- const actionConfig = await loadActionConfig(loadModule, filePath, exportName, res, requestId);
1532
- if (!actionConfig) return;
1533
- if (!enforceCsrfForAction(req, res, { actionConfig, csrfMode, disallowed, requestId })) {
1534
- return;
1535
- }
1536
- const pipeline = {
1537
- ctx,
1538
- buildPluginCtx,
1539
- pluginRunner,
1540
- serverDir,
1541
- loadModule,
1542
- req,
1543
- res
1544
- };
1545
- if (!await runPreHandlerPipeline(pipeline)) return;
1546
- ctx = pipeline.ctx;
1547
- const bodyOutcome = await readActionBody(req, res, requestId, actionConfig);
1548
- if (!bodyOutcome.ok) return;
1549
- const actionName = exportName === "default" ? deriveActionNameFromPath(filePath) : exportName;
1550
- const startedAt = Date.now();
1551
- const result = actionConfig.input.safeParse(bodyOutcome.body);
1552
- if (!result.success) {
1553
- const inputErr = new ActionInputError(result.error?.issues ?? []);
1554
- writeSerialized(res, serializeActionResult({ data: void 0, error: inputErr }));
1555
- await emitActionCallTelemetry(actionName, startedAt, bodyOutcome.body, {
1556
- status: "error",
1557
- error: inputErr
1558
- });
1559
- return;
1560
- }
1561
- await runActionHandler({
1562
- actionConfig,
1563
- actionName,
1564
- startedAt,
1565
- input: result.data,
1566
- ctx,
1567
- res,
1568
- pluginRunner,
1569
- buildPluginCtx
1570
- });
1571
- } catch (err) {
1572
- await handleActionError(err, { req, res, ctx, requestId, pluginRunner, buildPluginCtx });
1573
- }
1574
- }
1575
- function deriveActionNameFromPath(filePath) {
1576
- const base = filePath.split(/[\\/]/).pop() ?? "unknown";
1577
- return base.replace(/\.[jt]sx?$/, "");
1578
- }
1579
- function isActionErrorLike(err) {
1580
- if (err === null || typeof err !== "object") return false;
1581
- const obj = err;
1582
- return (obj.type === "TheoActionError" || obj.type === "TheoActionInputError") && typeof obj.code === "string" && typeof obj.status === "number";
1583
- }
1584
- async function runActionHandler(args) {
1585
- try {
1586
- const handlerResult = await args.actionConfig.handler({ input: args.input, ctx: args.ctx });
1587
- writeSerialized(args.res, serializeActionResult({ data: handlerResult, error: void 0 }));
1588
- if (args.pluginRunner) {
1589
- await args.pluginRunner.runOnResponse(args.buildPluginCtx(args.ctx));
1590
- }
1591
- await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
1592
- status: "success",
1593
- output: handlerResult
1594
- });
1595
- } catch (err) {
1596
- if (isActionErrorLike(err)) {
1597
- writeSerialized(args.res, serializeActionResult({ data: void 0, error: err }));
1598
- await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
1599
- status: "error",
1600
- error: err
1601
- });
1602
- return;
1603
- }
1604
- const wrapped = new ActionError({
1605
- code: "INTERNAL_SERVER_ERROR",
1606
- message: err instanceof Error ? err.message : "Action handler threw"
1607
- });
1608
- await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
1609
- status: "error",
1610
- error: wrapped
1611
- });
1612
- throw err;
1613
- }
1614
- }
1615
- async function handleActionError(err, c) {
1616
- return handleRequestError(err, {
1617
- req: c.req,
1618
- res: c.res,
1619
- requestId: c.requestId,
1620
- pluginRunner: c.pluginRunner,
1621
- buildPluginCtx: c.buildPluginCtx
1622
- });
1623
- }
1624
-
1625
- // src/server/observability/suggest.ts
1626
- function levenshtein(a, b) {
1627
- const m = a.length;
1628
- const n = b.length;
1629
- const dp = Array.from(
1630
- { length: m + 1 },
1631
- () => Array.from({ length: n + 1 }).fill(0)
1632
- );
1633
- for (let i = 0; i <= m; i++) dp[i][0] = i;
1634
- for (let j = 0; j <= n; j++) dp[0][j] = j;
1635
- for (let i = 1; i <= m; i++) {
1636
- for (let j = 1; j <= n; j++) {
1637
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1638
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
1639
- }
1640
- }
1641
- return dp[m][n];
1642
- }
1643
- function findSuggestion(input, candidates, maxDistance = 3) {
1644
- let best = null;
1645
- let bestDist = maxDistance + 1;
1646
- for (const c of candidates) {
1647
- const d = levenshtein(input, c);
1648
- if (d < bestDist) {
1649
- bestDist = d;
1650
- best = c;
1651
- }
1652
- }
1653
- return best;
1654
- }
1655
-
1656
- // src/server/scan/module-loader.ts
1657
- import { pathToFileURL } from "url";
1658
- function createViteLoader(vite) {
1659
- return (path) => vite.ssrLoadModule(path);
1660
- }
1661
- function createProductionLoader() {
1662
- return async (path) => {
1663
- const url = pathToFileURL(path).href;
1664
- return import(url);
1665
- };
1666
- }
1667
-
1668
- // src/server/http/batch-handler.ts
1669
- import { z as z2 } from "zod";
1670
- var STRIPPED_HEADERS = [
1671
- "authorization",
1672
- "cookie",
1673
- "x-forwarded-for",
1674
- "x-forwarded-host",
1675
- "x-forwarded-proto",
1676
- "x-real-ip",
1677
- "host"
1678
- ];
1679
- var BATCH_PATH = "/api/__theo_batch__";
1680
- var DEFAULT_MAX_BATCH = 32;
1681
- var BatchPathConflictError = class extends Error {
1682
- constructor(path) {
1683
- super(
1684
- `Server route conflicts with reserved batch path ${path}. Rename the route or disable batching in theo.config.ts.`
1685
- );
1686
- this.name = "BatchPathConflictError";
1687
- }
1688
- };
1689
- var batchRequestSchema = z2.object({
1690
- path: z2.string().min(1),
1691
- method: z2.string().min(1),
1692
- query: z2.record(z2.unknown()).optional(),
1693
- body: z2.unknown().optional(),
1694
- headers: z2.record(z2.string()).optional()
1695
- });
1696
- var batchPayloadSchema = z2.object({
1697
- requests: z2.array(batchRequestSchema).min(1)
1698
- });
1699
- function sanitizeItemHeaders(itemHeaders, outerHeaders) {
1700
- const out = {};
1701
- if (itemHeaders) {
1702
- for (const [k, v] of Object.entries(itemHeaders)) {
1703
- const lower = k.toLowerCase();
1704
- if (!STRIPPED_HEADERS.includes(lower)) {
1705
- out[lower] = v;
1706
- }
1707
- }
1708
- }
1709
- if (outerHeaders) {
1710
- for (const stripped of STRIPPED_HEADERS) {
1711
- if (Object.hasOwn(outerHeaders, stripped)) {
1712
- out[stripped] = outerHeaders[stripped];
1713
- }
1714
- }
1715
- }
1716
- return out;
1717
- }
1718
- async function handleBatchRequest(payload, options) {
1719
- const parsed = batchPayloadSchema.parse(payload);
1720
- const max = options.max ?? DEFAULT_MAX_BATCH;
1721
- if (parsed.requests.length > max) {
1722
- throw new Error(`Batch size ${parsed.requests.length} exceeds max ${max}`);
1723
- }
1724
- const results = [];
1725
- for (const item of parsed.requests) {
1726
- const sanitized = {
1727
- ...item,
1728
- headers: sanitizeItemHeaders(item.headers, options.outerHeaders)
1729
- };
1730
- try {
1731
- const r = await options.execute(sanitized);
1732
- results.push(r);
1733
- } catch (err) {
1734
- const message = err instanceof Error ? err.message : String(err);
1735
- results.push({ error: { message } });
1736
- }
1737
- }
1738
- return { results };
1739
- }
1740
-
1741
- // src/server/http/cors.ts
1742
- var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
1743
- var DEFAULT_ALLOWED_HEADERS = ["Content-Type", "X-Theo-Action", "Authorization"];
1744
- var DEFAULT_MAX_AGE = 600;
1745
- function readOrigin(req) {
1746
- const raw = req.headers.origin;
1747
- if (raw === void 0) return void 0;
1748
- if (Array.isArray(raw)) {
1749
- return raw.find((v) => typeof v === "string" && v.length > 0);
1750
- }
1751
- return raw;
1752
- }
1753
- function matchesOrigin(origin, allowed) {
1754
- if (allowed === "*") return true;
1755
- if (typeof allowed === "string") return origin === allowed;
1756
- if (allowed instanceof RegExp) {
1757
- allowed.lastIndex = 0;
1758
- return allowed.test(origin);
1759
- }
1760
- if (Array.isArray(allowed)) {
1761
- for (const entry of allowed) {
1762
- if (typeof entry === "string" && origin === entry) return true;
1763
- if (entry instanceof RegExp) {
1764
- entry.lastIndex = 0;
1765
- if (entry.test(origin)) return true;
1766
- }
1767
- }
1768
- return false;
1769
- }
1770
- if (typeof allowed === "function") {
1771
- try {
1772
- return allowed(origin);
1773
- } catch {
1774
- return false;
1775
- }
1776
- }
1777
- return false;
1778
- }
1779
- function createCorsHandler(config) {
1780
- const methods = (config.methods ?? DEFAULT_METHODS).slice();
1781
- const allowedHeaders = (config.allowedHeaders ?? DEFAULT_ALLOWED_HEADERS).slice();
1782
- const maxAge = String(config.maxAge ?? DEFAULT_MAX_AGE);
1783
- const credentials = config.credentials === true;
1784
- return {
1785
- handlePreflight(req, res) {
1786
- if (req.method !== "OPTIONS") return false;
1787
- const acMethod = req.headers["access-control-request-method"];
1788
- if (!acMethod) return false;
1789
- const origin = readOrigin(req);
1790
- if (!origin) return false;
1791
- if (!matchesOrigin(origin, config.origins)) {
1792
- res.statusCode = 403;
1793
- res.end();
1794
- return true;
1795
- }
1796
- res.setHeader("Access-Control-Allow-Origin", origin);
1797
- res.setHeader("Access-Control-Allow-Methods", methods.join(", "));
1798
- res.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
1799
- res.setHeader("Access-Control-Max-Age", maxAge);
1800
- res.setHeader("Vary", "Origin");
1801
- if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
1802
- res.statusCode = 204;
1803
- res.end();
1804
- return true;
1805
- },
1806
- applyHeaders(req, res) {
1807
- const origin = readOrigin(req);
1808
- if (!origin) return;
1809
- if (!matchesOrigin(origin, config.origins)) return;
1810
- res.setHeader("Access-Control-Allow-Origin", origin);
1811
- res.setHeader("Vary", "Origin");
1812
- if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
1813
- if (config.exposedHeaders && config.exposedHeaders.length > 0) {
1814
- res.setHeader("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
1815
- }
1816
- }
1817
- };
1818
- }
1819
-
1820
- // src/server/http/trace-context.ts
1821
- import { randomUUID } from "crypto";
1822
- var TRACE_HEADER = "x-trace-id";
1823
- var TRACE_PARENT_HEADER = "traceparent";
1824
- var REQUEST_ID_HEADER = "x-request-id";
1825
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
1826
- function parseTraceparent(value) {
1827
- if (!value) return null;
1828
- const m = TRACEPARENT_RE.exec(value);
1829
- if (!m) return null;
1830
- const traceId = m[1];
1831
- if (/^0+$/.test(traceId)) return null;
1832
- return traceId;
1833
- }
1834
- function pickHeader2(value) {
1835
- if (Array.isArray(value)) {
1836
- for (const v of value) {
1837
- if (typeof v === "string" && v.length > 0) return v;
1838
- }
1839
- return null;
1840
- }
1841
- if (typeof value === "string" && value.length > 0) return value;
1842
- return null;
1843
- }
1844
- function extractTraceId(req) {
1845
- const traceparent = pickHeader2(req.headers[TRACE_PARENT_HEADER]);
1846
- if (traceparent) {
1847
- const parsed = parseTraceparent(traceparent);
1848
- if (parsed) return parsed;
1849
- }
1850
- const requestId = pickHeader2(req.headers[REQUEST_ID_HEADER]);
1851
- if (requestId) return requestId;
1852
- return randomUUID();
1853
- }
1854
-
1855
- // src/server/rate-limit/rate-limit-store.ts
1856
- var InMemoryStore = class _InMemoryStore {
1857
- store = /* @__PURE__ */ new Map();
1858
- /** Bound the map to prevent unbounded growth from pathological inputs. */
1859
- static MAX_ENTRIES = 1e5;
1860
- /** GC sweep interval, milliseconds. */
1861
- static GC_INTERVAL_MS = 3e4;
1862
- gcTimer = null;
1863
- constructor() {
1864
- if (typeof setInterval !== "undefined") {
1865
- const timer = setInterval(() => {
1866
- this.sweepExpired();
1867
- }, _InMemoryStore.GC_INTERVAL_MS);
1868
- this.gcTimer = timer;
1869
- const maybeUnref = timer.unref;
1870
- if (typeof maybeUnref === "function") maybeUnref.call(timer);
1871
- }
1872
- }
1873
- /**
1874
- * Synchronous fast-path used by the legacy sync `createRateLimiter`
1875
- * surface (`api-middleware.ts` is sync). The async `incr` delegates to
1876
- * this for in-memory; external adapters override `incr` directly.
1877
- */
1878
- incrSync(key, windowMs) {
1879
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
1880
- throw new Error(
1881
- `InMemoryStore.incr: windowMs must be a positive finite number (got ${windowMs})`
1882
- );
1883
- }
1884
- const now = Date.now();
1885
- if (this.store.size >= _InMemoryStore.MAX_ENTRIES) {
1886
- const first = this.store.keys().next().value;
1887
- if (first !== void 0) this.store.delete(first);
1888
- }
1889
- const entry = this.store.get(key);
1890
- if (!entry || now >= entry.resetAt) {
1891
- const fresh = { count: 1, resetAt: now + windowMs };
1892
- this.store.set(key, fresh);
1893
- return { ...fresh };
1894
- }
1895
- entry.count++;
1896
- return { count: entry.count, resetAt: entry.resetAt };
1897
- }
1898
- /** Sweep expired entries. Called by the GC timer; safe to call manually. */
1899
- sweepExpired() {
1900
- const now = Date.now();
1901
- for (const [k, v] of this.store) {
1902
- if (v.resetAt <= now) this.store.delete(k);
1903
- }
1904
- }
1905
- /**
1906
- * Stop the GC timer. Call from tests or when discarding the store. After
1907
- * `dispose`, the store still works but no longer auto-sweeps.
1908
- */
1909
- dispose() {
1910
- if (this.gcTimer) {
1911
- clearInterval(this.gcTimer);
1912
- this.gcTimer = null;
1913
- }
1914
- }
1915
- // The async surface is required by the `RateLimitStore` interface
1916
- // (Redis/etc. adapters are inherently async). For the in-memory case
1917
- // we just adapt the sync implementations to Promises.
1918
- // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return
1919
- async incr(key, windowMs) {
1920
- return this.incrSync(key, windowMs);
1921
- }
1922
- // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return
1923
- async get(key) {
1924
- const now = Date.now();
1925
- const entry = this.store.get(key);
1926
- if (!entry) return null;
1927
- if (now >= entry.resetAt) return null;
1928
- return { count: entry.count, resetAt: entry.resetAt };
1929
- }
1930
- // eslint-disable-next-line @typescript-eslint/require-await -- interface contract: async return
1931
- async reset(key) {
1932
- this.store.delete(key);
1933
- }
1934
- };
1935
-
1936
- // src/server/rate-limit/rate-limit.ts
1937
- function createRateLimiter(config, opts = {}) {
1938
- const store = opts.store ?? new InMemoryStore();
1939
- const isInMemory = store instanceof InMemoryStore;
1940
- return function checkRateLimit(req) {
1941
- const key = req.socket?.remoteAddress ?? "unknown";
1942
- if (isInMemory) {
1943
- const state = store.incrSync(key, config.windowMs);
1944
- return resultFromState(state, config);
1945
- }
1946
- throw new Error(
1947
- "createRateLimiter: async RateLimitStore implementations are not supported by this sync fa\xE7ade. Use the InMemoryStore default or build a custom middleware around the async store directly."
1948
- );
1949
- };
1950
- }
1951
- function resultFromState(state, config) {
1952
- if (state.count > config.max) {
1953
- const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1e3);
1954
- return {
1955
- limited: true,
1956
- headers: {
1957
- "X-RateLimit-Limit": String(config.max),
1958
- "X-RateLimit-Remaining": "0",
1959
- "Retry-After": String(retryAfter)
1960
- }
1961
- };
1962
- }
1963
- return {
1964
- limited: false,
1965
- headers: {
1966
- "X-RateLimit-Limit": String(config.max),
1967
- "X-RateLimit-Remaining": String(Math.max(0, config.max - state.count))
1968
- }
1969
- };
1970
- }
1971
-
1972
- // src/server/security/csp-report.ts
1973
- var CSP_REPORT_PATH = "/__theo/csp-report";
1974
- var MAX_BODY = 16 * 1024;
1975
- function pickHeader3(value) {
1976
- if (typeof value === "string") return value;
1977
- if (Array.isArray(value) && value.length > 0) return value[0];
1978
- return "";
1979
- }
1980
- function toStringSafe(value, fallback = "(missing)") {
1981
- if (typeof value === "string") return value;
1982
- if (typeof value === "number" || typeof value === "boolean") return String(value);
1983
- return fallback;
1984
- }
1985
- function normalizeLegacy(raw) {
1986
- return {
1987
- blockedUrl: toStringSafe(raw["blocked-uri"]),
1988
- documentUrl: toStringSafe(raw["document-uri"]),
1989
- violatedDirective: toStringSafe(raw["violated-directive"]),
1990
- effectiveDirective: typeof raw["effective-directive"] === "string" ? raw["effective-directive"] : void 0,
1991
- originalPolicy: typeof raw["original-policy"] === "string" ? raw["original-policy"] : void 0,
1992
- disposition: raw.disposition === "enforce" || raw.disposition === "report" ? raw.disposition : void 0,
1993
- statusCode: typeof raw["status-code"] === "number" ? raw["status-code"] : void 0,
1994
- sourceFile: typeof raw["source-file"] === "string" ? raw["source-file"] : void 0,
1995
- lineNumber: typeof raw["line-number"] === "number" ? raw["line-number"] : void 0,
1996
- columnNumber: typeof raw["column-number"] === "number" ? raw["column-number"] : void 0
1997
- };
1998
- }
1999
- function normalizeNew(entry) {
2000
- if (!entry || typeof entry !== "object") return null;
2001
- const body = entry.body;
2002
- if (!body || typeof body !== "object") return null;
2003
- const b = body;
2004
- return {
2005
- blockedUrl: toStringSafe(b.blockedURL),
2006
- documentUrl: toStringSafe(b.documentURL),
2007
- violatedDirective: toStringSafe(b.violatedDirective),
2008
- effectiveDirective: typeof b.effectiveDirective === "string" ? b.effectiveDirective : void 0,
2009
- originalPolicy: typeof b.originalPolicy === "string" ? b.originalPolicy : void 0,
2010
- disposition: b.disposition === "enforce" || b.disposition === "report" ? b.disposition : void 0,
2011
- statusCode: typeof b.statusCode === "number" ? b.statusCode : void 0,
2012
- sourceFile: typeof b.sourceFile === "string" ? b.sourceFile : void 0,
2013
- lineNumber: typeof b.lineNumber === "number" ? b.lineNumber : void 0,
2014
- columnNumber: typeof b.columnNumber === "number" ? b.columnNumber : void 0
2015
- };
2016
- }
2017
- async function readBody(req, maxBytes) {
2018
- return await new Promise((resolve2, reject) => {
2019
- const chunks = [];
2020
- let total = 0;
2021
- req.on("data", (chunk) => {
2022
- total += chunk.length;
2023
- if (total > maxBytes) {
2024
- reject(new Error("body too large"));
2025
- req.destroy();
2026
- return;
2027
- }
2028
- chunks.push(chunk);
2029
- });
2030
- req.on("end", () => {
2031
- resolve2(Buffer.concat(chunks).toString("utf8"));
2032
- });
2033
- req.on("error", reject);
2034
- });
2035
- }
2036
- async function handleCspReport(req, res, opts) {
2037
- const ctHeader = req.headers["content-type"];
2038
- const ct = pickHeader3(ctHeader);
2039
- let raw;
2040
- try {
2041
- raw = await readBody(req, MAX_BODY);
2042
- } catch {
2043
- res.statusCode = 413;
2044
- res.end();
2045
- return;
2046
- }
2047
- let violations;
2048
- try {
2049
- if (ct.startsWith("application/csp-report")) {
2050
- const parsed = JSON.parse(raw);
2051
- const inner = parsed["csp-report"];
2052
- if (!inner || typeof inner !== "object") {
2053
- res.statusCode = 204;
2054
- res.end();
2055
- return;
2056
- }
2057
- violations = [normalizeLegacy(inner)];
2058
- } else if (ct.startsWith("application/reports+json")) {
2059
- const parsed = JSON.parse(raw);
2060
- const entries = Array.isArray(parsed) ? parsed : [];
2061
- violations = entries.map((e) => normalizeNew(e)).filter((v) => v !== null);
2062
- } else {
2063
- res.statusCode = 415;
2064
- res.end();
2065
- return;
2066
- }
2067
- } catch {
2068
- res.statusCode = 400;
2069
- res.end();
2070
- return;
2071
- }
2072
- for (const v of violations) {
2073
- safeAudit(opts.auditLogger, {
2074
- action: "csp.violation",
2075
- metadata: v
2076
- });
2077
- try {
2078
- opts.devtoolsDispatcher?.onCspViolation?.(v);
2079
- } catch {
2080
- }
2081
- try {
2082
- opts.onViolation?.(v);
2083
- } catch {
2084
- }
2085
- }
2086
- res.statusCode = 204;
2087
- res.end();
2088
- }
2089
-
2090
- // src/server/security/csrf-readiness-endpoint.ts
2091
- var CSRF_READINESS_PATH = "/__theo/csrf-readiness";
2092
- var CSRF_READINESS_RESET_PATH = "/__theo/csrf-readiness/reset";
2093
- function originMatchesHost(req) {
2094
- const origin = req.headers.origin;
2095
- if (typeof origin !== "string") return false;
2096
- const host = req.headers.host;
2097
- if (typeof host !== "string") return false;
2098
- try {
2099
- const parsed = new URL(origin);
2100
- return parsed.host === host;
2101
- } catch {
2102
- return false;
2103
- }
2104
- }
2105
- function sendJson2(res, status, body) {
2106
- res.writeHead(status, { "content-type": "application/json" });
2107
- res.end(JSON.stringify(body));
2108
- }
2109
- function sendNoContent(res) {
2110
- res.writeHead(204);
2111
- res.end();
2112
- }
2113
- function sendError2(res, status, code, message) {
2114
- res.writeHead(status, { "content-type": "application/json" });
2115
- res.end(JSON.stringify({ error: { code, message } }));
2116
- }
2117
- async function handleCsrfReadiness(req, res, store) {
2118
- const url = (req.url ?? "").split("?")[0];
2119
- const method = req.method ?? "GET";
2120
- if (url === CSRF_READINESS_PATH) {
2121
- if (method === "GET") {
2122
- sendJson2(res, 200, store.summary());
2123
- return true;
2124
- }
2125
- sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use GET on ${CSRF_READINESS_PATH}`);
2126
- return true;
2127
- }
2128
- if (url === CSRF_READINESS_RESET_PATH) {
2129
- if (method !== "POST") {
2130
- sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use POST on ${CSRF_READINESS_RESET_PATH}`);
2131
- return true;
2132
- }
2133
- const hasHeader = req.headers["x-theo-action"] === "1";
2134
- if (!hasHeader || !originMatchesHost(req)) {
2135
- sendError2(res, 403, "CSRF_INVALID", "Reset requires X-Theo-Action: 1 + same-origin");
2136
- return true;
2137
- }
2138
- store.reset();
2139
- sendNoContent(res);
2140
- return true;
2141
- }
2142
- return false;
2143
- }
2144
-
2145
- // src/server/security/security-headers.ts
2146
- var DEFAULT_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; report-uri /__theo/csp-report";
2147
- var DEFAULT_PERMISSIONS_POLICY = "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()";
2148
- var DEFAULT_HSTS = "max-age=31536000; includeSubDomains";
2149
- function applyNonceToCsp(policy, nonce) {
2150
- return policy.split(";").map((directive) => {
2151
- const trimmed = directive.trim();
2152
- if (!trimmed.startsWith("script-src")) return directive;
2153
- if (trimmed.includes("'unsafe-inline'")) {
2154
- return directive.replace("'unsafe-inline'", `'nonce-${nonce}'`);
2155
- }
2156
- return `${directive} 'nonce-${nonce}'`;
2157
- }).join(";");
2158
- }
2159
- function applyCsp(out, config, effectiveNonce) {
2160
- if (config.csp === false || config.cspMode === "off") return;
2161
- let policy = typeof config.csp === "string" ? config.csp : DEFAULT_CSP;
2162
- if (effectiveNonce) {
2163
- policy = applyNonceToCsp(policy, effectiveNonce);
2164
- }
2165
- const mode = config.cspMode ?? "enforce";
2166
- if (mode === "enforce") {
2167
- out["Content-Security-Policy"] = policy;
2168
- } else {
2169
- out["Content-Security-Policy-Report-Only"] = policy;
2170
- }
2171
- }
2172
- function buildSecurityHeaders(config, env, options = {}) {
2173
- const out = {};
2174
- const effectiveNonce = options.prerender ? void 0 : options.nonce;
2175
- applyCsp(out, config, effectiveNonce);
2176
- out["X-Frame-Options"] = config.frameOptions ?? "DENY";
2177
- out["X-Content-Type-Options"] = config.contentTypeOptions ?? "nosniff";
2178
- out["Referrer-Policy"] = config.referrerPolicy ?? "strict-origin-when-cross-origin";
2179
- if (config.permissionsPolicy !== false) {
2180
- out["Permissions-Policy"] = typeof config.permissionsPolicy === "string" ? config.permissionsPolicy : DEFAULT_PERMISSIONS_POLICY;
2181
- }
2182
- if (env.production && config.hsts !== false) {
2183
- out["Strict-Transport-Security"] = typeof config.hsts === "string" ? config.hsts : DEFAULT_HSTS;
2184
- }
2185
- if (effectiveNonce) {
2186
- out["Cache-Control"] = "private, no-store";
2187
- }
2188
- return out;
2189
- }
2190
- function applySecurityHeaders(res, config, env, options = {}) {
2191
- const headers = buildSecurityHeaders(config, env, options);
2192
- for (const [key, value] of Object.entries(headers)) {
2193
- res.setHeader(key, value);
2194
- }
2195
- }
2196
-
2197
- // src/server/plugins/plugin-runner.ts
2198
- var DuplicatePluginError = class extends Error {
2199
- constructor(name) {
2200
- super(`Plugin "${name}" is already registered.`);
2201
- this.name = "DuplicatePluginError";
2202
- }
2203
- };
2204
- var DuplicateDecorationError = class extends Error {
2205
- constructor(key, existingPlugin, newPlugin) {
2206
- super(
2207
- `Plugin "${newPlugin}" tried to decorate ctx.${key}, but it is already declared by plugin "${existingPlugin}".`
2208
- );
2209
- this.name = "DuplicateDecorationError";
2210
- }
2211
- };
2212
- var PluginRunner = class {
2213
- plugins = /* @__PURE__ */ new Set();
2214
- onRequestHooks = [];
2215
- preHandlerHooks = [];
2216
- onResponseHooks = [];
2217
- onErrorHooks = [];
2218
- decorations = /* @__PURE__ */ new Map();
2219
- has(name) {
2220
- return this.plugins.has(name);
2221
- }
2222
- async register(plugin) {
2223
- if (this.plugins.has(plugin.name)) {
2224
- throw new DuplicatePluginError(plugin.name);
2225
- }
2226
- this.plugins.add(plugin.name);
2227
- const app = {
2228
- addHook: (name, fn) => {
2229
- switch (name) {
2230
- case "onRequest":
2231
- this.onRequestHooks.push(fn);
2232
- return;
2233
- case "preHandler":
2234
- this.preHandlerHooks.push(fn);
2235
- return;
2236
- case "onResponse":
2237
- this.onResponseHooks.push(fn);
2238
- return;
2239
- case "onError":
2240
- this.onErrorHooks.push(fn);
2241
- return;
2242
- }
2243
- },
2244
- // `T` lets call sites bind a stronger value type via the plugin API.
2245
- // The runtime body stores `value` without inspection, so T appears
2246
- // only on the parameter — that is the intended contract.
2247
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the per-decoration type for consumers
2248
- decorateRequest: (key, value) => {
2249
- const existing = this.decorations.get(key);
2250
- if (existing) {
2251
- this.plugins.delete(plugin.name);
2252
- throw new DuplicateDecorationError(key, existing.pluginName, plugin.name);
2253
- }
2254
- this.decorations.set(key, { pluginName: plugin.name, value });
2255
- }
2256
- };
2257
- await plugin.register(app);
2258
- }
2259
- applyDecorations(ctx) {
2260
- for (const [key, entry] of this.decorations.entries()) {
2261
- ctx[key] = entry.value;
2262
- }
2263
- }
2264
- async runOnRequest(ctx) {
2265
- return this.runHookList(this.onRequestHooks, ctx);
2266
- }
2267
- async runPreHandler(ctx) {
2268
- return this.runHookList(this.preHandlerHooks, ctx);
2269
- }
2270
- async runOnResponse(ctx, options = {}) {
2271
- return this.runHookList(this.onResponseHooks, ctx, options);
2272
- }
2273
- /**
2274
- * Run all onError hooks. Swallows errors thrown inside hooks themselves to
2275
- * prevent recursion (an error in an error handler must not trigger onError
2276
- * again).
2277
- */
2278
- async runOnError(ctx, error) {
2279
- const errorCtx = { ...ctx, error };
2280
- for (const hook of this.onErrorHooks) {
2281
- try {
2282
- await hook(errorCtx);
2283
- } catch (innerErr) {
2284
- console.error(
2285
- `[plugin-runner] onError hook threw; suppressed to avoid recursion:`,
2286
- innerErr
2287
- );
2288
- }
2289
- }
2290
- return { shortCircuited: false };
2291
- }
2292
- async runHookList(hooks, ctx, options = {}) {
2293
- for (const hook of hooks) {
2294
- try {
2295
- await hook(ctx);
2296
- } catch (err) {
2297
- if (options.inErrorPath) {
2298
- console.error(`[plugin-runner] hook threw during error path; suppressed (EC-9):`, err);
2299
- continue;
2300
- }
2301
- throw err;
2302
- }
2303
- if (ctx.response.writableEnded || ctx.response.headersSent) {
2304
- return { shortCircuited: true };
2305
- }
2306
- }
2307
- return { shortCircuited: false };
2308
- }
2309
- };
2310
-
2311
- // src/server/plugins/load-plugins.ts
2312
- var InvalidPluginShapeError = class extends Error {
2313
- constructor(index, reason) {
2314
- super(`plugins[${index}] is not a valid TheoPlugin: ${reason}`);
2315
- this.name = "InvalidPluginShapeError";
2316
- }
2317
- };
2318
- function isPlugin(value, index) {
2319
- if (value == null || typeof value !== "object") {
2320
- throw new InvalidPluginShapeError(index, "expected an object");
2321
- }
2322
- const v = value;
2323
- if (typeof v.name !== "string" || v.name.length === 0) {
2324
- throw new InvalidPluginShapeError(index, 'missing "name" string');
2325
- }
2326
- if (typeof v.register !== "function") {
2327
- throw new InvalidPluginShapeError(index, 'missing "register" function');
2328
- }
2329
- return true;
2330
- }
2331
- async function createPluginRunnerFromConfig(plugins) {
2332
- if (plugins == null) return void 0;
2333
- if (!Array.isArray(plugins)) return void 0;
2334
- if (plugins.length === 0) return void 0;
2335
- const runner = new PluginRunner();
2336
- const pluginsArray = plugins;
2337
- for (let i = 0; i < pluginsArray.length; i++) {
2338
- const candidate = pluginsArray[i];
2339
- if (!isPlugin(candidate, i)) {
2340
- continue;
2341
- }
2342
- await runner.register(candidate);
2343
- }
2344
- return runner;
2345
- }
2346
-
2347
- // src/server/transformer.ts
2348
- import superjson from "superjson";
2349
- var superjsonTransformer = {
2350
- name: "superjson",
2351
- serialize: (v) => JSON.stringify(superjson.serialize(v)),
2352
- deserialize: (raw) => {
2353
- const parsed = JSON.parse(raw);
2354
- return superjson.deserialize(parsed);
2355
- }
2356
- };
2357
- var jsonTransformer = {
2358
- name: "json",
2359
- serialize: (v) => JSON.stringify(v),
2360
- deserialize: (raw) => JSON.parse(raw)
2361
- };
2362
- var BUILT_INS = {
2363
- superjson: superjsonTransformer,
2364
- json: jsonTransformer
2365
- };
2366
- function resolveTransformer(selector) {
2367
- if (typeof selector === "string") {
2368
- const built = BUILT_INS[selector];
2369
- if (built === void 0) {
2370
- throw new Error(
2371
- `Unknown transformer "${selector}". Built-in options: ${Object.keys(BUILT_INS).join(", ")}.`
2372
- );
2373
- }
2374
- return built;
2375
- }
2376
- if (typeof selector !== "object" || typeof selector.serialize !== "function" || typeof selector.deserialize !== "function") {
2377
- throw new Error(
2378
- `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`
2379
- );
2380
- }
2381
- return selector;
2382
- }
2383
-
2384
- export {
2385
- _resetEnvCache,
2386
- loadEnv,
2387
- CsrfReadinessStore,
2388
- ActionError,
2389
- ActionInputError,
2390
- extractUniversalIssues,
2391
- isActionError,
2392
- isInputError,
2393
- FileTooLargeError,
2394
- parseRequestBody,
2395
- logRequest,
2396
- createLogger,
2397
- _resetWarnOnceForTests,
2398
- warnOnce,
2399
- JsonStdoutSink,
2400
- createNoOpLogger,
2401
- safeAudit,
2402
- matchDisallowed,
2403
- CSRF_WARN_CODE,
2404
- CSRF_WARN_DOCS_URL,
2405
- validateCsrf,
2406
- enforceCsrf,
2407
- sendJson,
2408
- sendError,
2409
- scanMiddlewares,
2410
- _resetMiddlewareCacheForTests,
2411
- runMiddlewareAndContext,
2412
- executeRoute,
2413
- executeAction,
2414
- levenshtein,
2415
- findSuggestion,
2416
- createViteLoader,
2417
- createProductionLoader,
2418
- STRIPPED_HEADERS,
2419
- BATCH_PATH,
2420
- BatchPathConflictError,
2421
- handleBatchRequest,
2422
- matchesOrigin,
2423
- createCorsHandler,
2424
- TRACE_HEADER,
2425
- TRACE_PARENT_HEADER,
2426
- parseTraceparent,
2427
- extractTraceId,
2428
- InMemoryStore,
2429
- createRateLimiter,
2430
- CSP_REPORT_PATH,
2431
- normalizeLegacy,
2432
- normalizeNew,
2433
- handleCspReport,
2434
- CSRF_READINESS_PATH,
2435
- CSRF_READINESS_RESET_PATH,
2436
- handleCsrfReadiness,
2437
- DEFAULT_CSP,
2438
- DEFAULT_PERMISSIONS_POLICY,
2439
- buildSecurityHeaders,
2440
- applySecurityHeaders,
2441
- DuplicatePluginError,
2442
- DuplicateDecorationError,
2443
- PluginRunner,
2444
- InvalidPluginShapeError,
2445
- createPluginRunnerFromConfig,
2446
- superjsonTransformer,
2447
- jsonTransformer,
2448
- resolveTransformer
2449
- };
2450
- //# sourceMappingURL=chunk-CZM6WWWS.js.map