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,481 +1,770 @@
1
+ import {
2
+ buildServicesProxyConfig,
3
+ servicesConfigSchema
4
+ } from "./chunk-DNMSV3R3.js";
1
5
  import {
2
6
  broadcastToDevtools
3
7
  } from "./chunk-7BIM27LV.js";
4
8
  import {
5
- buildServicesProxyConfig,
6
- servicesConfigSchema
7
- } from "./chunk-O4BLVPML.js";
9
+ loadEnv,
10
+ resolveTransformer
11
+ } from "./chunk-PIVX3DYW.js";
12
+ import {
13
+ findSuggestion
14
+ } from "./chunk-5QW7IQQU.js";
15
+ import {
16
+ createPluginRunnerFromConfig
17
+ } from "./chunk-IAJ2JVEH.js";
18
+ import {
19
+ createRateLimiter
20
+ } from "./chunk-VMEWD57H.js";
21
+ import {
22
+ createViteLoader
23
+ } from "./chunk-JQSKBMXP.js";
24
+ import {
25
+ matchRoute,
26
+ scanServerRoutes,
27
+ scanWebSocketRoutes
28
+ } from "./chunk-OLPB36O2.js";
29
+ import {
30
+ scanServerActions
31
+ } from "./chunk-R7OC6UDK.js";
8
32
  import {
9
- BATCH_PATH,
10
33
  CSP_REPORT_PATH,
11
34
  CsrfReadinessStore,
12
- TRACE_HEADER,
13
35
  applySecurityHeaders,
36
+ handleCspReport,
37
+ handleCsrfReadiness
38
+ } from "./chunk-B3L3TJVF.js";
39
+ import {
40
+ generateNonce
41
+ } from "./chunk-C3ZZ56YZ.js";
42
+ import {
43
+ importUserModule
44
+ } from "./chunk-7MQOHNHE.js";
45
+ import {
46
+ BATCH_PATH,
47
+ TRACE_HEADER,
14
48
  createCorsHandler,
15
- createPluginRunnerFromConfig,
16
- createRateLimiter,
17
- createViteLoader,
18
49
  executeAction,
19
50
  executeRoute,
20
51
  extractTraceId,
21
- findSuggestion,
22
52
  handleBatchRequest,
23
- handleCspReport,
24
- handleCsrfReadiness,
25
- loadEnv,
26
- logRequest,
27
- resolveTransformer,
28
53
  sendError
29
- } from "./chunk-CZM6WWWS.js";
30
- import {
31
- matchRoute,
32
- scanServerRoutes,
33
- scanWebSocketRoutes
34
- } from "./chunk-B6RP57M3.js";
35
- import {
36
- scanServerActions
37
- } from "./chunk-7TOMTMHT.js";
54
+ } from "./chunk-5Z2727GV.js";
38
55
  import {
39
- generateNonce
40
- } from "./chunk-PB4GR7EP.js";
56
+ logRequest
57
+ } from "./chunk-Z5IGLBWE.js";
41
58
 
42
59
  // src/vite-plugin/index.ts
43
- import { existsSync as existsSync7 } from "fs";
44
- import { resolve as resolve6, basename, dirname as dirname4 } from "path";
60
+ import { existsSync as existsSync9 } from "fs";
61
+ import { resolve as resolve9, dirname as dirname4 } from "path";
45
62
  import { fileURLToPath } from "url";
46
63
 
47
- // src/devtools/server-side/route-manifest.ts
48
- function buildPath(parents, segment) {
49
- const joined = [...parents, segment].filter(Boolean).join("/");
50
- return "/" + joined;
51
- }
52
- function walk(node, parents, layoutChain, acc) {
53
- const path = buildPath(parents, node.segment);
54
- const nextChain = node.layout ? [...layoutChain, node.layout] : layoutChain;
55
- if (node.page) {
56
- acc.routes.push({
57
- path,
58
- absoluteFilePath: node.page,
59
- layoutChain: [...layoutChain],
60
- // freeze chain at this leaf
61
- hasLoading: Boolean(node.loading),
62
- hasError: Boolean(node.error),
63
- hasNotFound: Boolean(node.notFound)
64
- });
65
- }
66
- for (const child of node.children) {
67
- const nextParents = node.segment ? [...parents, node.segment] : parents;
68
- walk(child, nextParents, nextChain, acc);
69
- }
70
- }
71
- function buildRouteManifest(tree) {
72
- const acc = { routes: [] };
73
- walk(tree, [], [], acc);
74
- return { routes: acc.routes };
75
- }
76
- function broadcastRouteManifest(tree) {
77
- const manifest = buildRouteManifest(tree);
78
- broadcastToDevtools("theo:devtools:manifest", manifest);
64
+ // src/vite-plugin/config-hook.ts
65
+ import { existsSync } from "fs";
66
+ import { resolve } from "path";
67
+ function runConfigHook(ctx) {
68
+ const ext = existsSync(resolve(ctx.theoSrcDir, "index.ts")) ? ".ts" : ".js";
69
+ const warmupClientFiles = [];
70
+ for (const candidate of ["app/layout.tsx", "app/page.tsx", "app/page.jsx"]) {
71
+ const full = resolve(ctx.projectRoot, candidate);
72
+ if (existsSync(full)) warmupClientFiles.push(`./${candidate}`);
73
+ }
74
+ const servicesProxy = ctx.services && Object.keys(ctx.services).length > 0 ? buildServicesProxyConfig(ctx.services) : void 0;
75
+ return {
76
+ envPrefix: "THEO_PUBLIC_",
77
+ optimizeDeps: {
78
+ ...ctx.optimizeDepsInclude.length > 0 ? { include: ctx.optimizeDepsInclude } : {},
79
+ force: true
80
+ },
81
+ server: {
82
+ ...warmupClientFiles.length > 0 ? { warmup: { clientFiles: warmupClientFiles } } : {},
83
+ ...servicesProxy !== void 0 ? { proxy: servicesProxy } : {},
84
+ // Skip watching gitignored heavy dirs to avoid hitting ENOSPC
85
+ // (inotify watcher exhaustion).
86
+ watch: {
87
+ ignored: [
88
+ "**/referencias/**",
89
+ "**/.theokit/**",
90
+ "**/.theo/**",
91
+ "**/dist/**",
92
+ "**/node_modules/**"
93
+ ]
94
+ }
95
+ },
96
+ resolve: {
97
+ alias: [
98
+ // Order matters: most-specific first so `theokit/X` doesn't
99
+ // get matched by the bare `theokit` alias.
100
+ { find: "theokit/server", replacement: resolve(ctx.theoSrcDir, `server/index${ext}`) },
101
+ { find: "theokit/client", replacement: resolve(ctx.theoSrcDir, `client/index${ext}`) },
102
+ {
103
+ // T2.1 (M5 lonely folders) moved the source from
104
+ // `react-query/index.ts` into `client/react-query.ts` (sibling of
105
+ // `client/index.ts`). The package.json export `./react-query`
106
+ // still points at the build artifact `./dist/react-query/index.{js,d.ts}`,
107
+ // but the dev-time source alias must follow the new location.
108
+ find: "theokit/react-query",
109
+ replacement: resolve(ctx.theoSrcDir, `client/react-query${ext}`)
110
+ },
111
+ {
112
+ find: "theokit/vite-plugin",
113
+ replacement: resolve(ctx.theoSrcDir, `vite-plugin/index${ext}`)
114
+ },
115
+ {
116
+ find: "theokit/adapters/web-shim",
117
+ replacement: resolve(ctx.theoSrcDir, `adapters/web-shim${ext}`)
118
+ },
119
+ {
120
+ find: "theokit/adapters/ws-shim",
121
+ replacement: resolve(ctx.theoSrcDir, `adapters/ws-shim${ext}`)
122
+ },
123
+ // T1.2 — devtools entry (DEV only; tree-shaken in build).
124
+ // Source layout: src/devtools/dom/entry.tsx (has /dom/ segment)
125
+ // Dist layout: dist/devtools/entry.js (tsup flattens /dom/)
126
+ {
127
+ find: "theokit/devtools/entry",
128
+ replacement: resolve(
129
+ ctx.theoSrcDir,
130
+ ext === ".ts" ? "devtools/dom/entry.tsx" : "devtools/entry.js"
131
+ )
132
+ },
133
+ { find: "theokit", replacement: resolve(ctx.theoSrcDir, `index${ext}`) }
134
+ ]
135
+ }
136
+ };
79
137
  }
80
138
 
81
- // src/router/entry-server.ts
82
- function generateEntryServer(options = {}) {
83
- if (options.streaming) {
84
- return generateStreamingEntry(options);
85
- }
86
- return generateSingleShotEntry(options);
87
- }
88
- function buildAppTreeJs(options) {
89
- const theme = options.theoUi?.theme ?? "violet-forge";
90
- if (options.theoUi) {
91
- return [
92
- `React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },`,
93
- ` React.createElement(Suspense, { fallback: null },`,
94
- ` React.createElement(StaticRouterProvider, { router, context, hydrate: false })`,
95
- ` )`,
96
- ` )`
97
- ].join("\n");
139
+ // src/config/load-config.ts
140
+ import { existsSync as existsSync2 } from "fs";
141
+ import { resolve as resolve2 } from "path";
142
+
143
+ // src/config/errors.ts
144
+ var TheoConfigError = class extends Error {
145
+ issues;
146
+ configPath;
147
+ constructor(issues, configPath) {
148
+ const issueLines = issues.map((i) => ` - ${i.field}: ${i.message}`).join("\n");
149
+ super(
150
+ `Invalid theo.config.ts
151
+
152
+ File: ${configPath}
153
+
154
+ ` + (issueLines ? ` Issues:
155
+ ${issueLines}
156
+ ` : "")
157
+ );
158
+ this.name = "TheoConfigError";
159
+ this.issues = issues;
160
+ this.configPath = configPath;
98
161
  }
99
- return `React.createElement(Suspense, { fallback: null },
100
- React.createElement(StaticRouterProvider, { router, context, hydrate: false })
101
- )`;
102
- }
103
- function hydrationDataExtractSnippet() {
104
- return [
105
- ` const hydrationData = {`,
106
- ` loaderData: context.loaderData,`,
107
- ` actionData: context.actionData,`,
108
- ` errors: context.errors,`,
109
- ` }`
110
- ].join("\n");
111
- }
112
- function generateSingleShotEntry(options) {
113
- const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
114
- ` : "";
115
- const appTree = buildAppTreeJs(options);
116
- return [
117
- `import React, { Suspense } from 'react'`,
118
- `import { renderToPipeableStream } from 'react-dom/server'`,
119
- `import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
120
- `import { PassThrough } from 'node:stream'`,
121
- `import { routes } from '/@theo/route-manifest'`,
122
- theoUiImport,
123
- `export async function render(url, options = {}) {`,
124
- ` const handler = createStaticHandler(routes)`,
125
- ` const request = new Request('http://localhost' + url)`,
126
- ` const context = await handler.query(request)`,
127
- ``,
128
- ` if (context instanceof Response) {`,
129
- ` return { redirect: context }`,
130
- ` }`,
131
- ``,
132
- ` const router = createStaticRouter(handler.dataRoutes, context)`,
133
- ` const app = ${appTree}`,
134
- ``,
135
- hydrationDataExtractSnippet(),
136
- ``,
137
- ` return new Promise((resolve, reject) => {`,
138
- ` let html = ''`,
139
- ` let piped = false`,
140
- ` const passthrough = new PassThrough()`,
141
- ` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
142
- ` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
143
- ` passthrough.on('error', reject)`,
144
- ``,
145
- ` // Pipe on onShellReady (Next.js pattern). Calling pipe() twice`,
146
- ` // throws "React currently only supports piping to one writable`,
147
- ` // stream". The \`piped\` flag is a belt-and-suspenders guard if`,
148
- ` // onShellReady fires unexpectedly more than once.`,
149
- ` // Forward options.nonce to React so every <script> tag React`,
150
- ` // emits (Suspense boundary scripts) carries the nonce attribute.`,
151
- ` const { pipe } = renderToPipeableStream(app, {`,
152
- ` nonce: options.nonce,`,
153
- ` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
154
- ` onShellError(err) { reject(err) },`,
155
- ` onError(err) { console.error('[SSR Error]', err) },`,
156
- ` })`,
157
- ` })`,
158
- `}`
159
- ].join("\n");
160
- }
161
- function streamingWebRenderer(appTree) {
162
- return [
163
- `// T2.3 \u2014 Web Standards streaming entry for edge runtimes (Cloudflare,`,
164
- `// Bun, Deno, Vercel Edge). Uses renderToReadableStream and returns a`,
165
- `// Response with the stream as body. Honors request.signal for client`,
166
- `// disconnect cleanup.`,
167
- `export async function renderStreamingWeb(request, options = {}) {`,
168
- ` const handler = createStaticHandler(routes)`,
169
- ` const url = new URL(request.url)`,
170
- ` const context = await handler.query(request)`,
171
- ``,
172
- ` if (context instanceof Response) {`,
173
- ` return context`,
174
- ` }`,
175
- ``,
176
- ` const router = createStaticRouter(handler.dataRoutes, context)`,
177
- ` const app = ${appTree}`,
178
- ``,
179
- ` const stream = await renderToReadableStream(app, {`,
180
- ` signal: request.signal,`,
181
- ` nonce: options.nonce,`,
182
- ` onError(err) { console.error('[SSR Web Stream Error]', err) },`,
183
- ` })`,
184
- ` return new Response(stream, {`,
185
- ` status: 200,`,
186
- ` headers: {`,
187
- ` 'Content-Type': 'text/html; charset=utf-8',`,
188
- ` 'Transfer-Encoding': 'chunked',`,
189
- ` },`,
190
- ` })`,
191
- `}`
192
- ];
193
- }
194
- function streamingNodeRenderer(appTree) {
195
- return [
196
- `// T6.1 \u2014 Node streaming SSR entry (opt-in via theo.config.ts > ssrStreaming: true)`,
197
- `// Flushes the shell as soon as it's ready, then streams Suspense boundaries.`,
198
- `// EC-11: respects request.signal for client-disconnect cleanup.`,
199
- `export async function renderStreaming(url, response, options = {}) {`,
200
- ` const handler = createStaticHandler(routes)`,
201
- ` const request = new Request('http://localhost' + url, { signal: options.signal })`,
202
- ` const context = await handler.query(request)`,
203
- ``,
204
- ` if (context instanceof Response) {`,
205
- ` return { redirect: context }`,
206
- ` }`,
207
- ``,
208
- ` const router = createStaticRouter(handler.dataRoutes, context)`,
209
- ` const app = ${appTree}`,
210
- ``,
211
- ` return new Promise((resolve, reject) => {`,
212
- ` let didError = false`,
213
- ` const stream = renderToPipeableStream(app, {`,
214
- ` nonce: options.nonce,`,
215
- ` onShellReady() {`,
216
- ` response.statusCode = didError ? 500 : 200`,
217
- ` response.setHeader('Content-Type', 'text/html; charset=utf-8')`,
218
- ` response.setHeader('Transfer-Encoding', 'chunked')`,
219
- ` stream.pipe(response)`,
220
- ` resolve({ streaming: true })`,
221
- ` },`,
222
- ` onShellError(err) { reject(err) },`,
223
- ` onError(err) {`,
224
- ` didError = true`,
225
- ` console.error('[SSR Stream Error]', err)`,
226
- ` },`,
227
- ` })`,
228
- ``,
229
- ` // EC-11: client disconnect cleanup`,
230
- ` if (options.signal) {`,
231
- ` options.signal.addEventListener('abort', () => { stream.abort() })`,
232
- ` }`,
233
- ` })`,
234
- `}`
235
- ];
236
- }
237
- function backCompatRenderer(appTree) {
238
- return [
239
- `// Backward compatibility: keep the single-shot render export available so`,
240
- `// callers that always used 'render()' don't break when streaming is on.`,
241
- `export async function render(url, options = {}) {`,
242
- ` const handler = createStaticHandler(routes)`,
243
- ` const request = new Request('http://localhost' + url)`,
244
- ` const context = await handler.query(request)`,
245
- ``,
246
- ` if (context instanceof Response) {`,
247
- ` return { redirect: context }`,
248
- ` }`,
249
- ``,
250
- ` const router = createStaticRouter(handler.dataRoutes, context)`,
251
- ` const app = ${appTree}`,
252
- ``,
253
- hydrationDataExtractSnippet(),
254
- ``,
255
- ` const { PassThrough } = await import('node:stream')`,
256
- ` return new Promise((resolve, reject) => {`,
257
- ` let html = ''`,
258
- ` let piped = false`,
259
- ` const passthrough = new PassThrough()`,
260
- ` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
261
- ` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
262
- ` passthrough.on('error', reject)`,
263
- ``,
264
- ` // Pipe on onShellReady (Next.js pattern). nonce forwarded.`,
265
- ` const { pipe } = renderToPipeableStream(app, {`,
266
- ` nonce: options.nonce,`,
267
- ` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
268
- ` onShellError(err) { reject(err) },`,
269
- ` onError(err) { console.error('[SSR Error]', err) },`,
270
- ` })`,
271
- ` })`,
272
- `}`
273
- ];
274
- }
275
- function generateStreamingEntry(options) {
276
- const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
277
- ` : "";
278
- const appTree = buildAppTreeJs(options);
279
- return [
280
- `import React, { Suspense } from 'react'`,
281
- `import { renderToPipeableStream, renderToReadableStream } from 'react-dom/server'`,
282
- `import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
283
- `import { routes } from '/@theo/route-manifest'`,
284
- theoUiImport,
285
- ``,
286
- ...streamingWebRenderer(appTree),
287
- ``,
288
- ...streamingNodeRenderer(appTree),
289
- ``,
290
- ...backCompatRenderer(appTree)
291
- ].join("\n");
292
- }
162
+ };
293
163
 
294
- // src/router/entry.ts
295
- function generateEntryClient(ssr, opts = {}) {
296
- const rootMethod = ssr ? "hydrateRoot" : "createRoot";
297
- const theoUiImports = [];
298
- if (opts.theoUi) {
299
- theoUiImports.push(
300
- `// T2.2 \u2014 TheoUI CSS auto-injected (config.ui)`,
301
- `import '@theokit/ui/styles.css'`
302
- );
303
- const fontsModule = opts.theoUi.fonts === "cdn" ? "fonts-cdn.css" : "fonts.css";
304
- theoUiImports.push(
305
- `import '@theokit/ui/${fontsModule}'`,
306
- `// T2.3 \u2014 TheoUIProvider auto-wraps RouterProvider`,
307
- `import { TheoUIProvider } from '@theokit/ui'`
164
+ // src/config/schema.ts
165
+ import { z as z8 } from "zod";
166
+
167
+ // src/config/schemas/header-safe.ts
168
+ import { z } from "zod";
169
+ var headerSafeString = z.string().refine((s) => !/[\r\n]/.test(s), { message: "Header value must not contain CR/LF" });
170
+
171
+ // src/config/schemas/rate-limit.ts
172
+ import { z as z2 } from "zod";
173
+ var baseRateLimitSchema = z2.object({
174
+ windowMs: z2.number().min(1),
175
+ max: z2.number().int().min(1)
176
+ });
177
+ var rateLimitSchema = z2.union([
178
+ baseRateLimitSchema,
179
+ z2.object({
180
+ default: baseRateLimitSchema.optional(),
181
+ routes: z2.record(z2.string(), baseRateLimitSchema).optional(),
182
+ keyBy: z2.union([
183
+ z2.enum(["ip", "session", "user"]),
184
+ z2.function({ input: z2.tuple([z2.unknown()]), output: z2.string() })
185
+ ]).optional(),
186
+ cookieName: z2.string().min(1).optional()
187
+ })
188
+ ]);
189
+
190
+ // src/config/schemas/upload.ts
191
+ import { z as z3 } from "zod";
192
+ var uploadSchema = z3.object({
193
+ maxFileSize: z3.number().min(1).default(10 * 1024 * 1024),
194
+ // 10MB
195
+ maxFiles: z3.number().int().min(1).default(10),
196
+ maxFieldSize: z3.number().min(1).default(1 * 1024 * 1024)
197
+ // 1MB
198
+ });
199
+
200
+ // src/config/schemas/logging.ts
201
+ import { z as z4 } from "zod";
202
+ var loggingSchema = z4.object({
203
+ level: z4.enum(["debug", "info", "warn", "error", "silent"]).default("info")
204
+ });
205
+
206
+ // src/config/schemas/cache.ts
207
+ import { z as z5 } from "zod";
208
+ var routeRuleSchema = z5.object({
209
+ maxAge: z5.number().nonnegative().finite().optional(),
210
+ swr: z5.number().nonnegative().finite().optional(),
211
+ tags: z5.array(z5.string()).optional()
212
+ });
213
+ var cacheSchema = z5.object({
214
+ enabled: z5.boolean().default(true),
215
+ /** 'memory' uses InMemoryCacheAdapter; otherwise pass a custom adapter instance. */
216
+ storage: z5.union([z5.literal("memory"), z5.custom()]).default("memory"),
217
+ maxEntries: z5.number().int().positive().default(1e3),
218
+ defaults: z5.object({
219
+ maxAge: z5.number().nonnegative().finite().default(1),
220
+ swr: z5.number().nonnegative().finite().optional(),
221
+ cacheErrors: z5.boolean().default(false)
222
+ }).default({ maxAge: 1, cacheErrors: false }),
223
+ keyDerivation: z5.object({
224
+ excludeQuery: z5.array(z5.string()).optional(),
225
+ sortQuery: z5.boolean().default(true),
226
+ lowercaseHost: z5.boolean().default(true)
227
+ }).default({ sortQuery: true, lowercaseHost: true }),
228
+ routeRules: z5.record(z5.string(), routeRuleSchema).optional()
229
+ });
230
+
231
+ // src/config/schemas/security.ts
232
+ import { z as z6 } from "zod";
233
+ var securityHeadersSchema = z6.object({
234
+ csp: z6.union([headerSafeString, z6.literal(false)]).optional(),
235
+ // T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.
236
+ // Users who want the old behaviour set `cspMode: 'report-only'`
237
+ // explicitly. See docs/migrating/0.2-to-0.3.md.
238
+ cspMode: z6.enum(["enforce", "report-only", "off"]).default("enforce"),
239
+ hsts: z6.union([headerSafeString, z6.literal(false)]).optional(),
240
+ frameOptions: z6.enum(["DENY", "SAMEORIGIN"]).default("DENY"),
241
+ contentTypeOptions: z6.literal("nosniff").default("nosniff"),
242
+ referrerPolicy: headerSafeString.default("strict-origin-when-cross-origin"),
243
+ /**
244
+ * T1.1 — Permissions-Policy directive string. EC-3-refined: rejects CR/LF.
245
+ * Pass `false` to suppress the header.
246
+ */
247
+ permissionsPolicy: z6.union([headerSafeString, z6.literal(false)]).optional()
248
+ });
249
+ var disallowedConfigSchema = z6.object({
250
+ routes: z6.array(z6.union([z6.string(), z6.instanceof(RegExp)])),
251
+ behavior: z6.enum(["warn", "raise"]).default("raise")
252
+ });
253
+ var corsSchema = z6.object({
254
+ origins: z6.union([
255
+ z6.literal("*"),
256
+ headerSafeString,
257
+ z6.instanceof(RegExp),
258
+ z6.array(z6.union([headerSafeString, z6.instanceof(RegExp)])),
259
+ z6.function({ input: z6.tuple([z6.string()]), output: z6.boolean() })
260
+ ]),
261
+ methods: z6.array(z6.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"])).optional(),
262
+ allowedHeaders: z6.array(headerSafeString).optional(),
263
+ exposedHeaders: z6.array(headerSafeString).optional(),
264
+ credentials: z6.boolean().default(false),
265
+ maxAge: z6.number().int().min(0).max(86400).default(600)
266
+ }).refine((c) => !(c.origins === "*" && c.credentials), {
267
+ message: 'CORS spec forbids origins:"*" with credentials:true \u2014 browsers ignore the wildcard'
268
+ });
269
+ var securitySchema = z6.object({
270
+ // T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that
271
+ // grep their warn-mode logs from 0.2.x already know which endpoints
272
+ // break; opt back into 'warn' globally OR use disallowedRoutes for
273
+ // surgical migration. See docs/migrating/0.2-to-0.3.md.
274
+ csrf: z6.enum(["off", "warn", "strict"]).default("strict"),
275
+ headers: securityHeadersSchema.optional(),
276
+ /** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */
277
+ disallowed: disallowedConfigSchema.optional(),
278
+ /** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */
279
+ cors: corsSchema.optional()
280
+ });
281
+
282
+ // src/config/schemas/storage.ts
283
+ import { z as z7 } from "zod";
284
+ var tlsConfigSchema = z7.object({
285
+ rejectUnauthorized: z7.boolean().optional(),
286
+ caCert: z7.string().optional(),
287
+ clientCert: z7.string().optional(),
288
+ clientKey: z7.string().optional()
289
+ });
290
+ var serverConfigSchema = z7.object({
291
+ host: z7.string().min(1),
292
+ port: z7.number().int().positive().max(65535).optional(),
293
+ user: z7.string().min(1),
294
+ /** Password may be the empty string (some providers permit it). */
295
+ password: z7.string(),
296
+ tls: tlsConfigSchema.optional()
297
+ });
298
+ var postgresPoolConfigSchema = z7.object({
299
+ min: z7.number().int().nonnegative().optional(),
300
+ max: z7.number().int().positive().optional(),
301
+ connectionTimeoutMillis: z7.number().int().positive().optional(),
302
+ idleTimeoutMillis: z7.number().int().nonnegative().optional()
303
+ });
304
+ var postgresDatabaseConfigSchema = z7.object({
305
+ /** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */
306
+ server: z7.string().min(1),
307
+ database: z7.string().min(1),
308
+ pool: postgresPoolConfigSchema.optional()
309
+ });
310
+ var redisServerConfigSchema = serverConfigSchema.extend({
311
+ db: z7.number().int().nonnegative().optional(),
312
+ maxRetriesPerRequest: z7.number().int().nonnegative().optional()
313
+ });
314
+ var storageSchema = z7.object({
315
+ servers: z7.record(z7.string(), serverConfigSchema).optional(),
316
+ databases: z7.record(z7.string(), postgresDatabaseConfigSchema).optional(),
317
+ redis: z7.record(z7.string(), redisServerConfigSchema).optional()
318
+ });
319
+
320
+ // src/config/schema.ts
321
+ var theoConfigSchema = z8.object({
322
+ /**
323
+ * Plan v1.2 T2.1 — project identifier propagated into
324
+ * `.theo/services.json` v2 `project` field. DNS-1123 compatible (drives
325
+ * Gitea repo + ArgoCD App naming on TheoCloud). When omitted, the build
326
+ * emits services.json v1 with the legacy `services-bundle` fallback
327
+ * (ADR D10) and logs a deprecation warning.
328
+ */
329
+ name: z8.string().max(63).refine(
330
+ // DNS-1123 equivalent expressed as explicit single-char anchors so
331
+ // security/detect-unsafe-regex stays clean (no backtracking).
332
+ (value) => value.length > 0 && /^[a-z0-9-]+$/u.test(value) && !value.startsWith("-") && !value.endsWith("-"),
333
+ "name must match DNS-1123 (lowercase alphanumeric+hyphens, 1-63 chars, no leading/trailing hyphen)"
334
+ ).optional(),
335
+ appDir: z8.string().default("app"),
336
+ serverDir: z8.string().default("server"),
337
+ /**
338
+ * T2.2 / EC-4 — Build output directory. Must be a relative path inside
339
+ * the project root. Refused absolute or parent-relative paths to prevent
340
+ * `cleanOutDir` from wiping arbitrary locations (defense-in-depth on
341
+ * top of cleanOutDir's runtime EC-3 guard).
342
+ */
343
+ distDir: z8.string().default(".theo").refine(
344
+ (d) => !/^([A-Za-z]:)?[/\\]/.test(d) && !d.startsWith(".."),
345
+ 'distDir must be a relative path inside the project root (e.g., ".theo")'
346
+ ),
347
+ /**
348
+ * Agent runtime configuration.
349
+ *
350
+ * - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale
351
+ * `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's
352
+ * native registry GC but kept for backward-compat.
353
+ * - `registry` (Phase 6, Production-Readiness #2): forwarded to
354
+ * `Agent.registry.configure()` lazily on first request.
355
+ * * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations
356
+ * (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts
357
+ * mid-stream → SDK aborts with code:'aborted'. Default 100 covers
358
+ * indie/small-team; tune up for high-traffic.
359
+ * * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables
360
+ * idle eviction (LRU only).
361
+ */
362
+ agents: z8.object({
363
+ maxRegistries: z8.number().int().positive().default(100),
364
+ registry: z8.object({
365
+ /** LRU cap — MUST be ≥ max-concurrent-active-conversations. */
366
+ maxAgents: z8.number().int().positive().max(1e4).default(100),
367
+ /** Idle eviction window in ms. 0 disables idle eviction (LRU only). */
368
+ idleTimeoutMs: z8.number().int().nonnegative().default(30 * 6e4)
369
+ }).optional()
370
+ }).optional(),
371
+ port: z8.number().int().min(1).max(65535).default(3e3),
372
+ ssr: z8.boolean().default(false),
373
+ /** When true (and ssr === true), use renderToPipeableStream with progressive
374
+ * shell flush instead of single-shot renderToString. Opt-in for streaming
375
+ * SSR; default false preserves the current behavior. */
376
+ ssrStreaming: z8.boolean().default(false),
377
+ rateLimit: rateLimitSchema.optional(),
378
+ upload: uploadSchema.optional(),
379
+ logging: loggingSchema.optional(),
380
+ security: securitySchema.optional(),
381
+ serialization: z8.enum(["json", "superjson"]).default("json"),
382
+ // Plugins are validated structurally at runtime by createPluginRunnerFromConfig.
383
+ // Zod only checks the shape minimally (must be array). Type-level safety is
384
+ // provided through defineConfig at the user surface.
385
+ plugins: z8.array(z8.unknown()).optional(),
386
+ /** Enable client-side batching of theoFetch calls and the
387
+ * /api/__theo_batch__ server endpoint. */
388
+ batching: z8.union([z8.boolean(), z8.object({ max: z8.number().int().positive().optional() })]).optional(),
389
+ /** T4.1 — Audit log. When `logger` is provided, framework events
390
+ * (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are
391
+ * emitted to it. Default: noop. */
392
+ audit: z8.object({
393
+ logger: z8.unknown().optional()
394
+ }).optional(),
395
+ /** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;
396
+ * undefined = enabled when @theokit/ui is detected in node_modules. */
397
+ ui: z8.union([
398
+ z8.literal(false),
399
+ z8.object({
400
+ theme: z8.enum(["violet-forge", "noir", "paper"]).optional(),
401
+ fonts: z8.enum(["bundled", "cdn"]).optional()
402
+ })
403
+ ]).optional(),
404
+ /**
405
+ * Cache subsystem (caching-and-revalidation-plan).
406
+ * Default `undefined` keeps caching disabled (backward compatible).
407
+ * Pass `cache: {}` to opt in with defaults.
408
+ */
409
+ cache: cacheSchema.optional(),
410
+ /**
411
+ * Devtools overlay (Phase 0.4.0+ — see docs/plans/devtools-plan.md).
412
+ *
413
+ * - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.
414
+ * - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).
415
+ * - `{ ... }`: devtools enabled with explicit defaults (position, theme).
416
+ *
417
+ * Tree-shaken to noop in prod via the dual-export pattern in
418
+ * `packages/theo/src/devtools/index.ts` (EC-17 positive prod check).
419
+ */
420
+ devtools: z8.union([
421
+ z8.literal(false),
422
+ z8.object({
423
+ position: z8.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional(),
424
+ theme: z8.enum(["light", "dark", "system"]).optional()
425
+ })
426
+ ]).optional(),
427
+ /**
428
+ * Informative list of deploy adapters the app supports. Does NOT
429
+ * trigger per-build translations — only the `--target` CLI flag does
430
+ * (EC-201 / ADR D2). If `config.adapters` includes a target NOT
431
+ * matching `--target`, `theokit build` emits a cross-reference note.
432
+ */
433
+ adapters: z8.array(z8.string()).optional(),
434
+ /**
435
+ * Extra package names to pre-bundle via Vite's `optimizeDeps.include`.
436
+ *
437
+ * Framework auto-includes `@theokit/ui` and `lucide-react` when present.
438
+ * Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`
439
+ * (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers
440
+ * installing `mermaid` for Mermaid SVG rendering — must declare those
441
+ * here so Vite can resolve the literal in dev mode without
442
+ * "Failed to resolve module specifier" errors. Production builds use
443
+ * tsup/esbuild bundling and are not affected.
444
+ *
445
+ * Example:
446
+ * viteOptimizeDeps: ['mermaid']
447
+ */
448
+ viteOptimizeDeps: z8.array(z8.string()).optional(),
449
+ /**
450
+ * Jobs backend (ADR D3 + T2.1). When configured, every request gets
451
+ * `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a
452
+ * `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).
453
+ */
454
+ jobs: z8.object({
455
+ backend: z8.custom((v) => typeof v === "object" && v !== null)
456
+ }).optional(),
457
+ /**
458
+ * StorageManager configuration (T1.1 / ADR-0007).
459
+ *
460
+ * Declares Postgres servers + databases + Redis servers. Consumed by
461
+ * `getStorageManager().configure()` at boot via `start.ts`.
462
+ *
463
+ * Default `undefined` means manager exists but is not configured — adapters
464
+ * relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will
465
+ * throw with an actionable error on first use.
466
+ */
467
+ storage: storageSchema.optional(),
468
+ /**
469
+ * Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).
470
+ *
471
+ * Declarative external sidecar processes (Python FastAPI / Node Hono)
472
+ * that boot alongside the TheoKit TS app. Empty `services: {}` is the
473
+ * default and preserves Wave 1 behavior.
474
+ */
475
+ services: servicesConfigSchema,
476
+ /**
477
+ * G2 — OpenAPI emit (build-time only).
478
+ *
479
+ * When present, `theokit build` writes a generated `openapi.json` from
480
+ * every `defineRoute()` body/query/params Zod schema. `undefined` keeps
481
+ * the framework backward-compatible (no emit). Pass `openapi: {}` to
482
+ * opt in with defaults.
483
+ *
484
+ * Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
485
+ * "TheoKit App" · version "0.0.0" · outDir ".theo" (next to dist).
486
+ *
487
+ * The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
488
+ * `servers[].url` at emit time without rebuilding the config.
489
+ */
490
+ openapi: z8.object({
491
+ servers: z8.array(
492
+ z8.object({
493
+ url: z8.string().url(),
494
+ description: z8.string().optional()
495
+ })
496
+ ).default([{ url: "http://localhost:3000", description: "Local development" }]),
497
+ specVersion: z8.enum(["3.1.0", "3.0.3"]).default("3.1.0"),
498
+ title: z8.string().default("TheoKit App"),
499
+ version: z8.string().default("0.0.0"),
500
+ outDir: z8.string().default(".theo")
501
+ }).optional(),
502
+ /**
503
+ * G5 T1.3 — error envelope transformer hook (blueprint ADR D3).
504
+ *
505
+ * When present, runs at framework error-boundary time to enrich the
506
+ * envelope with consumer-defined extensions (`hint`, custom telemetry
507
+ * tags, etc.) BEFORE the envelope is serialized to the client. The
508
+ * function signature is preserved via the explicit `FormatErrorHook`
509
+ * type — TS inference flows from `theo.config.ts` into `@theo/client`
510
+ * codegen and `@theokit/ui` AgentErrorCard consumers.
511
+ *
512
+ * Use `z.custom<FormatErrorHook>(...)` because Zod's built-in
513
+ * `z.function()` discards its TS signature after parse; `z.custom`
514
+ * with a function-typed runtime guard preserves the call-site type
515
+ * without trading off Zod runtime validation.
516
+ */
517
+ formatError: z8.custom((val) => typeof val === "function", {
518
+ message: "formatError must be a function"
519
+ }).optional()
520
+ }).refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {
521
+ message: "service.port collides with TheoKit web port \u2014 change one of them",
522
+ path: ["services"]
523
+ });
524
+
525
+ // src/config/load-config.ts
526
+ var CONFIG_FILE = "theo.config.ts";
527
+ function deepMerge(base, override) {
528
+ const result = { ...base };
529
+ for (const key of Object.keys(override)) {
530
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
531
+ const baseVal = base[key];
532
+ const overVal = override[key];
533
+ if (overVal !== null && typeof overVal === "object" && !Array.isArray(overVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
534
+ result[key] = deepMerge(
535
+ baseVal,
536
+ overVal
537
+ );
538
+ } else {
539
+ result[key] = overVal;
540
+ }
541
+ }
542
+ return result;
543
+ }
544
+ async function loadConfig(dir) {
545
+ loadEnv({ cwd: dir });
546
+ const configPath = resolve2(dir, CONFIG_FILE);
547
+ if (!existsSync2(configPath)) {
548
+ return theoConfigSchema.parse({});
549
+ }
550
+ let mod;
551
+ try {
552
+ mod = await importUserModule(configPath);
553
+ } catch (err) {
554
+ throw new TheoConfigError([{ field: "_file", message: err.message }], configPath);
555
+ }
556
+ const userConfig = mod.default;
557
+ if (userConfig == null || typeof userConfig !== "object") {
558
+ throw new TheoConfigError(
559
+ [
560
+ {
561
+ field: "_export",
562
+ message: "theo.config.ts must use export default defineConfig({...})"
563
+ }
564
+ ],
565
+ configPath
308
566
  );
309
567
  }
310
- const theme = opts.theoUi?.theme ?? "violet-forge";
311
- const routerTree = opts.theoUi ? `React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },
312
- React.createElement(Suspense, { fallback: null },
313
- React.createElement(RouterProvider, { router })
314
- )
315
- )` : `React.createElement(Suspense, { fallback: null },
316
- React.createElement(RouterProvider, { router })
317
- )`;
318
- const renderCall = ssr ? ` ${rootMethod}(el,
319
- ${routerTree}
320
- )` : ` ${rootMethod}(el).render(
321
- ${routerTree}
322
- )`;
323
- const hydrationLine = ssr ? `const router = createBrowserRouter(routes, { hydrationData: window.__staticRouterHydrationData })` : `const router = createBrowserRouter(routes)`;
324
- const preloadBlock = ssr ? [
325
- `// Phase 4 \u2014 preload matched-route modules before hydrate (EC-3 safeguard)`,
326
- `const __theoMatches = matchRoutes(routes, window.location.pathname) ?? []`,
327
- `const __theoPreloadPaths = __theoMatches`,
328
- ` .map((m) => m.route && (m.route).path)`,
329
- ` .filter((p) => typeof p === 'string' && p in __theoPreloadMap)`,
330
- `const __theoPreloadPromise = Promise.all(`,
331
- ` __theoPreloadPaths.map((p) => __theoPreloadMap[p]().catch((err) => { console.error('[theo] preload failed', p, err); return null }))`,
332
- `)`,
333
- `const __theoTimeout = new Promise((resolve) => setTimeout(() => resolve('timeout'), 1500))`,
334
- `await Promise.race([__theoPreloadPromise, __theoTimeout])`,
335
- ""
336
- ] : [];
337
- const renderBlock = ssr ? [
338
- `if (el) {`,
339
- ` ;(async () => {`,
340
- ...preloadBlock.map((l) => " " + l),
341
- renderCall,
342
- ` })()`,
343
- `}`
344
- ] : [`if (el) {`, renderCall, `}`];
345
- const manifestImports = ssr ? `import { routes, __theoPreloadMap } from '/@theo/route-manifest'` : `import { routes } from '/@theo/route-manifest'`;
346
- const reactRouterImports = ssr ? `import { createBrowserRouter, RouterProvider, matchRoutes } from 'react-router'` : `import { createBrowserRouter, RouterProvider } from 'react-router'`;
347
- return [
348
- `import React, { Suspense } from 'react'`,
349
- `import { ${rootMethod} } from 'react-dom/client'`,
350
- reactRouterImports,
351
- manifestImports,
352
- `// T1.3 \u2014 side-effect import sets globalThis.__THEO_TRANSFORMER__ for theoFetch`,
353
- `import '/@theo/runtime-config'`,
354
- ...theoUiImports,
355
- ``,
356
- hydrationLine,
357
- `const el = document.getElementById('root')`,
358
- ...renderBlock
359
- ].join("\n");
568
+ let rawConfig = userConfig;
569
+ const nodeEnv = process.env.NODE_ENV;
570
+ if (nodeEnv) {
571
+ const envFile = `theo.config.${nodeEnv}.ts`;
572
+ const envPath = resolve2(dir, envFile);
573
+ if (existsSync2(envPath)) {
574
+ try {
575
+ const envMod = await importUserModule(envPath);
576
+ const envConfig = envMod.default;
577
+ if (envConfig != null && typeof envConfig === "object") {
578
+ rawConfig = deepMerge(rawConfig, envConfig);
579
+ }
580
+ } catch (err) {
581
+ throw new TheoConfigError([{ field: "_file", message: err.message }], envPath);
582
+ }
583
+ }
584
+ }
585
+ const result = theoConfigSchema.safeParse(rawConfig);
586
+ if (!result.success) {
587
+ const issues = result.error.issues.map((i) => ({
588
+ field: i.path.join("."),
589
+ message: i.message
590
+ }));
591
+ throw new TheoConfigError(issues, configPath);
592
+ }
593
+ return result.data;
360
594
  }
361
595
 
362
- // src/router/generate.ts
363
- function normalizePath(p) {
364
- return p.replace(/\\/g, "/");
365
- }
366
- function safeVarName(segment, prefix) {
367
- const safe = segment.replace(/[^a-zA-Z0-9]/g, "_") || "root";
368
- return `${prefix}_${safe}`;
369
- }
370
- function buildRoutePath(parents, segment) {
371
- const joined = [...parents, segment].filter(Boolean).join("/");
372
- return "/" + joined;
596
+ // src/vite-plugin/theoui-detect.ts
597
+ import { existsSync as existsSync3, readFileSync } from "fs";
598
+ import { dirname, join } from "path";
599
+ function resolveTheoUiConfig(raw) {
600
+ return {
601
+ theme: raw?.theme ?? "violet-forge",
602
+ fonts: raw?.fonts ?? "bundled"
603
+ };
373
604
  }
374
- function pushIf(staticImports, filePath, varName) {
375
- if (filePath !== void 0) {
376
- staticImports.push({ varName, importPath: normalizePath(filePath) });
605
+ function resolveExportSubpath(pkgRoot, subpath) {
606
+ const pkgJsonPath = join(pkgRoot, "package.json");
607
+ if (!existsSync3(pkgJsonPath)) return null;
608
+ try {
609
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
610
+ const exportKey = `./${subpath}`;
611
+ const exp = pkg.exports?.[exportKey];
612
+ if (!exp) {
613
+ const fallback = join(pkgRoot, "dist", subpath);
614
+ return existsSync3(fallback) ? fallback : null;
615
+ }
616
+ let target;
617
+ if (typeof exp === "string") target = exp;
618
+ else if (typeof exp === "object") {
619
+ const e = exp;
620
+ target = e.import ?? e.default;
621
+ }
622
+ if (!target) return null;
623
+ const cleaned = target.replace(/^\.\//, "");
624
+ const candidate = join(pkgRoot, cleaned);
625
+ return existsSync3(candidate) ? candidate : null;
626
+ } catch {
627
+ return null;
377
628
  }
378
629
  }
379
- function walkRouteTree(node, parents, acc) {
380
- const seg = node.segment || "root";
381
- const routePath = buildRoutePath(parents, node.segment);
382
- if (node.page !== void 0) {
383
- acc.lazyPages.push({
384
- varName: safeVarName(seg, "Page"),
385
- importPath: normalizePath(node.page),
386
- routePath
387
- });
630
+ var defaultResolver = (specifier, projectRoot) => {
631
+ const parts = specifier.split("/");
632
+ const pkgName = parts[0]?.startsWith("@") && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
633
+ if (!pkgName) return false;
634
+ const subpath = specifier.slice(pkgName.length + 1);
635
+ if (!subpath) return false;
636
+ let dir = projectRoot;
637
+ for (let depth = 0; depth < 10; depth++) {
638
+ const pkgRoot = join(dir, "node_modules", ...pkgName.split("/"));
639
+ if (existsSync3(pkgRoot)) {
640
+ const resolved = resolveExportSubpath(pkgRoot, subpath);
641
+ if (resolved) return true;
642
+ }
643
+ const parent = dirname(dir);
644
+ if (parent === dir) break;
645
+ dir = parent;
388
646
  }
389
- if (node.layout !== void 0) acc.layoutState.found = true;
390
- pushIf(acc.staticImports, node.layout, safeVarName(seg, "Layout"));
391
- pushIf(acc.staticImports, node.error, safeVarName(seg, "Error"));
392
- pushIf(acc.staticImports, node.loading, safeVarName(seg, "Loading"));
393
- pushIf(acc.staticImports, node.notFound, safeVarName(seg, "NotFound"));
394
- for (const child of node.children) {
395
- const nextParents = node.segment ? [...parents, node.segment] : parents;
396
- walkRouteTree(child, nextParents, acc);
647
+ return false;
648
+ };
649
+ function isDeclaredInPackageJson(projectRoot) {
650
+ try {
651
+ const raw = readFileSync(join(projectRoot, "package.json"), "utf-8");
652
+ const pkg = JSON.parse(raw);
653
+ return Boolean(
654
+ pkg.dependencies?.["@theokit/ui"] ?? pkg.devDependencies?.["@theokit/ui"] ?? pkg.peerDependencies?.["@theokit/ui"]
655
+ );
656
+ } catch {
657
+ return false;
397
658
  }
398
659
  }
399
- function generateRouteManifest(tree) {
400
- const acc = {
401
- staticImports: [],
402
- lazyPages: [],
403
- layoutState: { found: false }
404
- };
405
- walkRouteTree(tree, [], acc);
406
- const staticImports = acc.staticImports;
407
- const lazyPages = acc.lazyPages;
408
- const layoutState = acc.layoutState;
409
- const lines = [`import React, { Suspense } from 'react'`];
410
- if (layoutState.found) {
411
- lines.push(`import { Outlet } from 'react-router'`);
412
- }
413
- lines.push("");
414
- for (const imp of staticImports) {
415
- lines.push(`import ${imp.varName} from '${imp.importPath}'`);
660
+ function detectTheoUi(projectRoot, rawConfig, resolver = defaultResolver) {
661
+ const config = resolveTheoUiConfig(typeof rawConfig === "object" ? rawConfig : void 0);
662
+ if (rawConfig === false) {
663
+ return { enabled: false, config };
416
664
  }
417
- for (const lp of lazyPages) {
418
- lines.push(`const ${lp.varName} = React.lazy(() => import('${lp.importPath}'))`);
665
+ if (!isDeclaredInPackageJson(projectRoot)) {
666
+ return { enabled: false, config };
419
667
  }
420
- lines.push("");
421
- const preloadEntries = lazyPages.map(
422
- (lp) => ` '${lp.routePath}': () => import('${lp.importPath}'),`
423
- );
424
- lines.push("export const __theoPreloadMap = {");
425
- for (const e of preloadEntries) lines.push(e);
426
- lines.push("}", "");
427
- function buildChildrenArray(node, seg) {
428
- const childConfigs = [];
429
- if (node.page) {
430
- const pageVar = safeVarName(seg, "Page");
431
- const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
432
- const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
433
- childConfigs.push(`{ index: true, element: ${pageElement} }`);
434
- }
435
- for (const child of node.children) {
436
- childConfigs.push(genRouteConfig(child, false));
668
+ const probes = ["@theokit/ui/styles.css", "@theokit/ui/fonts.css"];
669
+ for (const probe of probes) {
670
+ if (resolver(probe, projectRoot)) {
671
+ return { enabled: true, config };
437
672
  }
438
- if (node.notFound) {
439
- const nfVar = safeVarName(seg, "NotFound");
440
- childConfigs.push(`{ path: '*', element: React.createElement(${nfVar}) }`);
673
+ }
674
+ return { enabled: false, config };
675
+ }
676
+
677
+ // src/vite-plugin/config-resolve.ts
678
+ var DEFAULT_RESOLVED = {
679
+ pluginRunner: void 0,
680
+ transformer: void 0,
681
+ resolvedBatching: void 0,
682
+ theoUi: void 0,
683
+ csrfMode: "strict",
684
+ securityHeaders: void 0,
685
+ disallowed: void 0,
686
+ cors: void 0,
687
+ auditLogger: void 0,
688
+ devtoolsEnabled: true,
689
+ openapi: void 0,
690
+ distDir: ".theo"
691
+ };
692
+ async function resolvePluginConfig(projectRoot) {
693
+ try {
694
+ const userConfig = await loadConfig(projectRoot);
695
+ const pluginRunner = await createPluginRunnerFromConfig(userConfig.plugins);
696
+ const transformer = resolveTransformer(userConfig.serialization);
697
+ let resolvedBatching;
698
+ if (userConfig.batching === true) {
699
+ resolvedBatching = {};
700
+ } else if (typeof userConfig.batching === "object") {
701
+ resolvedBatching = userConfig.batching;
441
702
  }
442
- let arr = `[${childConfigs.join(", ")}]`;
443
- if (node.error) {
444
- const errVar = safeVarName(seg, "Error");
445
- arr = `[{ errorElement: React.createElement(${errVar}), children: ${arr} }]`;
703
+ const theoUi = detectTheoUi(projectRoot, userConfig.ui);
704
+ let auditLogger;
705
+ const maybeLogger = userConfig.audit?.logger;
706
+ if (maybeLogger && typeof maybeLogger.log === "function") {
707
+ auditLogger = maybeLogger;
446
708
  }
447
- return arr;
709
+ return {
710
+ pluginRunner,
711
+ transformer,
712
+ resolvedBatching,
713
+ theoUi,
714
+ csrfMode: userConfig.security?.csrf ?? "strict",
715
+ securityHeaders: userConfig.security?.headers,
716
+ disallowed: userConfig.security?.disallowed,
717
+ cors: userConfig.security?.cors,
718
+ auditLogger,
719
+ devtoolsEnabled: userConfig.devtools !== false,
720
+ openapi: userConfig.openapi,
721
+ distDir: userConfig.distDir
722
+ };
723
+ } catch {
724
+ return DEFAULT_RESOLVED;
725
+ }
726
+ }
727
+
728
+ // src/vite-plugin/configure-server-hook.ts
729
+ import { resolve as resolve6, basename } from "path";
730
+
731
+ // src/devtools/server-side/route-manifest.ts
732
+ function buildPath(parents, segment) {
733
+ const joined = [...parents, segment].filter(Boolean).join("/");
734
+ return "/" + joined;
735
+ }
736
+ function walk(node, parents, layoutChain, acc) {
737
+ const path = buildPath(parents, node.segment);
738
+ const nextChain = node.layout ? [...layoutChain, node.layout] : layoutChain;
739
+ if (node.page) {
740
+ acc.routes.push({
741
+ path,
742
+ absoluteFilePath: node.page,
743
+ layoutChain: [...layoutChain],
744
+ // freeze chain at this leaf
745
+ hasLoading: Boolean(node.loading),
746
+ hasError: Boolean(node.error),
747
+ hasNotFound: Boolean(node.notFound)
748
+ });
448
749
  }
449
- function genRouteConfig(node, isRoot) {
450
- const seg = node.segment || "root";
451
- const childrenArray = buildChildrenArray(node, seg);
452
- if (node.layout) {
453
- const layoutVar = safeVarName(seg, "Layout");
454
- const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
455
- return `{ ${pathPart}, element: React.createElement(${layoutVar}, { children: React.createElement(Outlet) }), children: ${childrenArray} }`;
456
- }
457
- if (isRoot) {
458
- if (node.children.length === 0 && !node.page && !node.notFound && !node.error) {
459
- return `{ path: '/', children: [] }`;
460
- }
461
- return `{ path: '/', children: ${childrenArray} }`;
462
- }
463
- if (node.page && node.children.length === 0 && !node.error && !node.notFound) {
464
- const pageVar = safeVarName(seg, "Page");
465
- const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
466
- const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
467
- return `{ path: '${node.segment}', element: ${pageElement} }`;
468
- }
469
- return `{ path: '${node.segment}', children: ${childrenArray} }`;
750
+ for (const child of node.children) {
751
+ const nextParents = node.segment ? [...parents, node.segment] : parents;
752
+ walk(child, nextParents, nextChain, acc);
470
753
  }
471
- const routeConfig = genRouteConfig(tree, true);
472
- lines.push(`export const routes = [${routeConfig}]`);
473
- return lines.join("\n");
754
+ }
755
+ function buildRouteManifest(tree) {
756
+ const acc = { routes: [] };
757
+ walk(tree, [], [], acc);
758
+ return { routes: acc.routes };
759
+ }
760
+ function broadcastRouteManifest(tree) {
761
+ const manifest = buildRouteManifest(tree);
762
+ broadcastToDevtools("theo:devtools:manifest", manifest);
474
763
  }
475
764
 
476
765
  // src/router/scan.ts
477
- import { readdirSync, statSync, existsSync } from "fs";
478
- import { join, resolve } from "path";
766
+ import { readdirSync, statSync, existsSync as existsSync4 } from "fs";
767
+ import { join as join2, resolve as resolve3 } from "path";
479
768
 
480
769
  // src/router/types.ts
481
770
  var ROUTE_FILE_NAMES = ["page", "layout", "error", "loading", "not-found"];
@@ -515,8 +804,8 @@ function attachRouteFiles(node, dir) {
515
804
  if (node[key] !== void 0) continue;
516
805
  for (const ext of ROUTE_FILE_EXTENSIONS) {
517
806
  const filename = `${name}${ext}`;
518
- if (existsSync(join(dir, filename))) {
519
- setRouteFile(node, key, resolve(dir, filename));
807
+ if (existsSync4(join2(dir, filename))) {
808
+ setRouteFile(node, key, resolve3(dir, filename));
520
809
  break;
521
810
  }
522
811
  }
@@ -531,8 +820,10 @@ function scanDir(dir, segment, routePath) {
531
820
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
532
821
  if (!entry.isDirectory()) continue;
533
822
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
534
- const childPath = routePath === "/" ? `/${entry.name}` : `${routePath}/${entry.name}`;
535
- const child = scanDir(join(dir, entry.name), entry.name, childPath);
823
+ const isRouteGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
824
+ const urlSegment = isRouteGroup ? "" : entry.name;
825
+ const childPath = urlSegment ? routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}` : routePath;
826
+ const child = scanDir(join2(dir, entry.name), isRouteGroup ? "" : entry.name, childPath);
536
827
  if (nodeHasContent(child)) {
537
828
  node.children.push(child);
538
829
  }
@@ -540,7 +831,7 @@ function scanDir(dir, segment, routePath) {
540
831
  return node;
541
832
  }
542
833
  function scanRoutes(appDir) {
543
- if (!existsSync(appDir)) {
834
+ if (!existsSync4(appDir)) {
544
835
  throw new Error(`App directory does not exist: ${appDir}`);
545
836
  }
546
837
  if (!statSync(appDir).isDirectory()) {
@@ -680,10 +971,10 @@ async function handleBatchIfMatch(req, res, ctx) {
680
971
  async function handleBatchInline(req, res, batching, requestId) {
681
972
  try {
682
973
  const chunks = [];
683
- await new Promise((resolve7, reject) => {
974
+ await new Promise((resolve10, reject) => {
684
975
  req.on("data", (c) => chunks.push(c));
685
976
  req.on("end", () => {
686
- resolve7();
977
+ resolve10();
687
978
  });
688
979
  req.on("error", reject);
689
980
  });
@@ -829,542 +1120,483 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
829
1120
  };
830
1121
  }
831
1122
 
832
- // src/config/load-config.ts
833
- import { existsSync as existsSync2 } from "fs";
834
- import { resolve as resolve2 } from "path";
835
- import { pathToFileURL } from "url";
836
-
837
- // src/config/errors.ts
838
- var TheoConfigError = class extends Error {
839
- issues;
840
- configPath;
841
- constructor(issues, configPath) {
842
- const issueLines = issues.map((i) => ` - ${i.field}: ${i.message}`).join("\n");
843
- super(
844
- `Invalid theo.config.ts
845
-
846
- File: ${configPath}
847
-
848
- ` + (issueLines ? ` Issues:
849
- ${issueLines}
850
- ` : "")
851
- );
852
- this.name = "TheoConfigError";
853
- this.issues = issues;
854
- this.configPath = configPath;
855
- }
856
- };
1123
+ // src/vite-plugin/ssr-dev-middleware.ts
1124
+ import { readFileSync as readFileSync2 } from "fs";
1125
+ import { resolve as resolve4 } from "path";
1126
+ function isSsrRenderResult(value) {
1127
+ if (typeof value !== "object" || value === null) return false;
1128
+ if (!("html" in value)) return false;
1129
+ return typeof value.html === "string";
1130
+ }
1131
+ function setupSsrDevMiddleware(server, opts) {
1132
+ server.middlewares.use((req, res, next) => {
1133
+ void (async () => {
1134
+ const url = req.url ?? "/";
1135
+ if (url.startsWith("/api/") || url.startsWith("/@") || url.startsWith("/node_modules/") || url.includes(".")) {
1136
+ next();
1137
+ return;
1138
+ }
1139
+ try {
1140
+ const indexPath = resolve4(opts.projectRoot, "index.html");
1141
+ let template = readFileSync2(indexPath, "utf-8");
1142
+ template = await server.transformIndexHtml(url, template);
1143
+ const nonce = generateNonce();
1144
+ applySecurityHeaders(
1145
+ res,
1146
+ opts.securityHeaders ?? {},
1147
+ { production: process.env.NODE_ENV === "production" },
1148
+ { nonce }
1149
+ );
1150
+ const mod = await server.ssrLoadModule(opts.virtualEntryServerId);
1151
+ const result = await mod.render(url, { nonce });
1152
+ if (result && typeof result === "object" && "redirect" in result) {
1153
+ res.writeHead(302, {
1154
+ Location: result.redirect.headers.get("location") ?? "/"
1155
+ });
1156
+ res.end();
1157
+ return;
1158
+ }
1159
+ let ssrHtml;
1160
+ let hydrationScript = "";
1161
+ if (typeof result === "string") {
1162
+ ssrHtml = result;
1163
+ } else if (isSsrRenderResult(result)) {
1164
+ ssrHtml = result.html;
1165
+ const dataJson = JSON.stringify(result.hydrationData).replace(/</g, "\\u003c");
1166
+ hydrationScript = `<script nonce="${nonce}">window.__staticRouterHydrationData=${dataJson}</script>`;
1167
+ } else {
1168
+ ssrHtml = "";
1169
+ }
1170
+ const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
1171
+ if (!rootDivMatch) {
1172
+ res.writeHead(200, { "Content-Type": "text/html" });
1173
+ res.end(template);
1174
+ return;
1175
+ }
1176
+ const splitIdx = template.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
1177
+ const html = template.slice(0, splitIdx) + ssrHtml + hydrationScript + template.slice(splitIdx);
1178
+ res.writeHead(200, { "Content-Type": "text/html" });
1179
+ res.end(html);
1180
+ } catch (err) {
1181
+ server.ssrFixStacktrace(err);
1182
+ console.error("[SSR Dev Error]", err);
1183
+ next();
1184
+ return;
1185
+ }
1186
+ })();
1187
+ });
1188
+ }
857
1189
 
858
- // src/config/schema.ts
859
- import { z } from "zod";
860
- var baseRateLimitSchema = z.object({
861
- windowMs: z.number().min(1),
862
- max: z.number().int().min(1)
863
- });
864
- var rateLimitSchema = z.union([
865
- baseRateLimitSchema,
866
- z.object({
867
- default: baseRateLimitSchema.optional(),
868
- routes: z.record(z.string(), baseRateLimitSchema).optional(),
869
- keyBy: z.union([
870
- z.enum(["ip", "session", "user"]),
871
- z.function().args(z.unknown()).returns(z.string())
872
- ]).optional(),
873
- cookieName: z.string().min(1).optional()
874
- })
875
- ]);
876
- var uploadSchema = z.object({
877
- maxFileSize: z.number().min(1).default(10 * 1024 * 1024),
878
- // 10MB
879
- maxFiles: z.number().int().min(1).default(10),
880
- maxFieldSize: z.number().min(1).default(1 * 1024 * 1024)
881
- // 1MB
882
- });
883
- var loggingSchema = z.object({
884
- level: z.enum(["debug", "info", "warn", "error", "silent"]).default("info")
885
- });
886
- var routeRuleSchema = z.object({
887
- maxAge: z.number().nonnegative().finite().optional(),
888
- swr: z.number().nonnegative().finite().optional(),
889
- tags: z.array(z.string()).optional()
890
- });
891
- var cacheSchema = z.object({
892
- enabled: z.boolean().default(true),
893
- /** 'memory' uses InMemoryCacheAdapter; otherwise pass a custom adapter instance. */
894
- storage: z.union([z.literal("memory"), z.custom()]).default("memory"),
895
- maxEntries: z.number().int().positive().default(1e3),
896
- defaults: z.object({
897
- maxAge: z.number().nonnegative().finite().default(1),
898
- swr: z.number().nonnegative().finite().optional(),
899
- cacheErrors: z.boolean().default(false)
900
- }).default({}),
901
- keyDerivation: z.object({
902
- excludeQuery: z.array(z.string()).optional(),
903
- sortQuery: z.boolean().default(true),
904
- lowercaseHost: z.boolean().default(true)
905
- }).default({}),
906
- routeRules: z.record(z.string(), routeRuleSchema).optional()
907
- });
908
- var headerSafeString = z.string().refine((s) => !/[\r\n]/.test(s), { message: "Header value must not contain CR/LF" });
909
- var securityHeadersSchema = z.object({
910
- csp: z.union([headerSafeString, z.literal(false)]).optional(),
911
- // T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.
912
- // Users who want the old behaviour set `cspMode: 'report-only'`
913
- // explicitly. See docs/migrating/0.2-to-0.3.md.
914
- cspMode: z.enum(["enforce", "report-only", "off"]).default("enforce"),
915
- hsts: z.union([headerSafeString, z.literal(false)]).optional(),
916
- frameOptions: z.enum(["DENY", "SAMEORIGIN"]).default("DENY"),
917
- contentTypeOptions: z.literal("nosniff").default("nosniff"),
918
- referrerPolicy: headerSafeString.default("strict-origin-when-cross-origin"),
919
- /**
920
- * T1.1 — Permissions-Policy directive string. EC-3-refined: rejects CR/LF.
921
- * Pass `false` to suppress the header.
922
- */
923
- permissionsPolicy: z.union([headerSafeString, z.literal(false)]).optional()
924
- });
925
- var disallowedConfigSchema = z.object({
926
- routes: z.array(z.union([z.string(), z.instanceof(RegExp)])),
927
- behavior: z.enum(["warn", "raise"]).default("raise")
928
- });
929
- var corsSchema = z.object({
930
- origins: z.union([
931
- z.literal("*"),
932
- headerSafeString,
933
- z.instanceof(RegExp),
934
- z.array(z.union([headerSafeString, z.instanceof(RegExp)])),
935
- z.function().args(z.string()).returns(z.boolean())
936
- ]),
937
- methods: z.array(z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"])).optional(),
938
- allowedHeaders: z.array(headerSafeString).optional(),
939
- exposedHeaders: z.array(headerSafeString).optional(),
940
- credentials: z.boolean().default(false),
941
- maxAge: z.number().int().min(0).max(86400).default(600)
942
- }).refine((c) => !(c.origins === "*" && c.credentials), {
943
- message: 'CORS spec forbids origins:"*" with credentials:true \u2014 browsers ignore the wildcard'
944
- });
945
- var securitySchema = z.object({
946
- // T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that
947
- // grep their warn-mode logs from 0.2.x already know which endpoints
948
- // break; opt back into 'warn' globally OR use disallowedRoutes for
949
- // surgical migration. See docs/migrating/0.2-to-0.3.md.
950
- csrf: z.enum(["off", "warn", "strict"]).default("strict"),
951
- headers: securityHeadersSchema.optional(),
952
- /** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */
953
- disallowed: disallowedConfigSchema.optional(),
954
- /** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */
955
- cors: corsSchema.optional()
956
- });
957
- var tlsConfigSchema = z.object({
958
- rejectUnauthorized: z.boolean().optional(),
959
- caCert: z.string().optional(),
960
- clientCert: z.string().optional(),
961
- clientKey: z.string().optional()
962
- });
963
- var serverConfigSchema = z.object({
964
- host: z.string().min(1),
965
- port: z.number().int().positive().max(65535).optional(),
966
- user: z.string().min(1),
967
- /** Password may be the empty string (some providers permit it). */
968
- password: z.string(),
969
- tls: tlsConfigSchema.optional()
970
- });
971
- var postgresPoolConfigSchema = z.object({
972
- min: z.number().int().nonnegative().optional(),
973
- max: z.number().int().positive().optional(),
974
- connectionTimeoutMillis: z.number().int().positive().optional(),
975
- idleTimeoutMillis: z.number().int().nonnegative().optional()
976
- });
977
- var postgresDatabaseConfigSchema = z.object({
978
- /** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */
979
- server: z.string().min(1),
980
- database: z.string().min(1),
981
- pool: postgresPoolConfigSchema.optional()
982
- });
983
- var redisServerConfigSchema = serverConfigSchema.extend({
984
- db: z.number().int().nonnegative().optional(),
985
- maxRetriesPerRequest: z.number().int().nonnegative().optional()
986
- });
987
- var storageSchema = z.object({
988
- servers: z.record(z.string(), serverConfigSchema).optional(),
989
- databases: z.record(z.string(), postgresDatabaseConfigSchema).optional(),
990
- redis: z.record(z.string(), redisServerConfigSchema).optional()
991
- });
992
- var theoConfigSchema = z.object({
993
- appDir: z.string().default("app"),
994
- serverDir: z.string().default("server"),
995
- /**
996
- * T2.2 / EC-4 — Build output directory. Must be a relative path inside
997
- * the project root. Refused absolute or parent-relative paths to prevent
998
- * `cleanOutDir` from wiping arbitrary locations (defense-in-depth on
999
- * top of cleanOutDir's runtime EC-3 guard).
1000
- */
1001
- distDir: z.string().default(".theo").refine(
1002
- (d) => !/^([A-Za-z]:)?[/\\]/.test(d) && !d.startsWith(".."),
1003
- 'distDir must be a relative path inside the project root (e.g., ".theo")'
1004
- ),
1005
- /**
1006
- * Agent runtime configuration.
1007
- *
1008
- * - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale
1009
- * `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's
1010
- * native registry GC but kept for backward-compat.
1011
- * - `registry` (Phase 6, Production-Readiness #2): forwarded to
1012
- * `Agent.registry.configure()` lazily on first request.
1013
- * * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations
1014
- * (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts
1015
- * mid-stream → SDK aborts with code:'aborted'. Default 100 covers
1016
- * indie/small-team; tune up for high-traffic.
1017
- * * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables
1018
- * idle eviction (LRU only).
1019
- */
1020
- agents: z.object({
1021
- maxRegistries: z.number().int().positive().default(100),
1022
- registry: z.object({
1023
- /** LRU cap — MUST be ≥ max-concurrent-active-conversations. */
1024
- maxAgents: z.number().int().positive().max(1e4).default(100),
1025
- /** Idle eviction window in ms. 0 disables idle eviction (LRU only). */
1026
- idleTimeoutMs: z.number().int().nonnegative().default(30 * 6e4)
1027
- }).optional()
1028
- }).optional(),
1029
- port: z.number().int().min(1).max(65535).default(3e3),
1030
- ssr: z.boolean().default(false),
1031
- /** When true (and ssr === true), use renderToPipeableStream with progressive
1032
- * shell flush instead of single-shot renderToString. Opt-in for streaming
1033
- * SSR; default false preserves the current behavior. */
1034
- ssrStreaming: z.boolean().default(false),
1035
- rateLimit: rateLimitSchema.optional(),
1036
- upload: uploadSchema.optional(),
1037
- logging: loggingSchema.optional(),
1038
- security: securitySchema.optional(),
1039
- serialization: z.enum(["json", "superjson"]).default("json"),
1040
- // Plugins are validated structurally at runtime by createPluginRunnerFromConfig.
1041
- // Zod only checks the shape minimally (must be array). Type-level safety is
1042
- // provided through defineConfig at the user surface.
1043
- plugins: z.array(z.unknown()).optional(),
1044
- /** Enable client-side batching of theoFetch calls and the
1045
- * /api/__theo_batch__ server endpoint. */
1046
- batching: z.union([z.boolean(), z.object({ max: z.number().int().positive().optional() })]).optional(),
1047
- /** T4.1 — Audit log. When `logger` is provided, framework events
1048
- * (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are
1049
- * emitted to it. Default: noop. */
1050
- audit: z.object({
1051
- logger: z.unknown().optional()
1052
- }).optional(),
1053
- /** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;
1054
- * undefined = enabled when @theokit/ui is detected in node_modules. */
1055
- ui: z.union([
1056
- z.literal(false),
1057
- z.object({
1058
- theme: z.enum(["violet-forge", "noir", "paper"]).optional(),
1059
- fonts: z.enum(["bundled", "cdn"]).optional()
1190
+ // src/vite-plugin/ws-upgrade.ts
1191
+ import { resolve as resolve5 } from "path";
1192
+ function setupWsUpgrade(server, projectRoot) {
1193
+ const wsRoutes = scanWebSocketRoutes(resolve5(projectRoot, "server"));
1194
+ if (wsRoutes.length === 0) return;
1195
+ const wsHttpServer = server.httpServer;
1196
+ if (!wsHttpServer) return;
1197
+ void import("ws").then(({ WebSocketServer }) => {
1198
+ const wss = new WebSocketServer({ noServer: true });
1199
+ wsHttpServer.on("upgrade", (request, socket, head) => {
1200
+ void (async () => {
1201
+ const url = request.url ?? "/";
1202
+ if (!url.startsWith("/ws/")) return;
1203
+ const wsPath = url.split("?")[0];
1204
+ const match = wsRoutes.find((r) => r.wsPath === wsPath);
1205
+ if (!match) {
1206
+ socket.destroy();
1207
+ return;
1208
+ }
1209
+ try {
1210
+ const mod = await server.ssrLoadModule(match.filePath);
1211
+ const handler = mod.default ?? mod;
1212
+ wss.handleUpgrade(request, socket, head, (ws) => {
1213
+ void handler.onOpen?.(ws, request);
1214
+ ws.on("message", (data) => {
1215
+ void handler.onMessage?.(ws, data.toString());
1216
+ });
1217
+ ws.on("close", (code, reason) => {
1218
+ void handler.onClose?.(ws, code, reason);
1219
+ });
1220
+ ws.on("error", (err) => {
1221
+ void handler.onError?.(ws, err);
1222
+ });
1223
+ });
1224
+ } catch {
1225
+ socket.destroy();
1226
+ }
1227
+ })();
1228
+ });
1229
+ }).catch(() => {
1230
+ console.warn(
1231
+ '[Theo] WebSocket routes found but "ws" package not installed. Run: npm install ws'
1232
+ );
1233
+ });
1234
+ }
1235
+
1236
+ // src/vite-plugin/configure-server-hook.ts
1237
+ async function runConfigureServer(server, ctx) {
1238
+ ctx.isDevMode.value = true;
1239
+ globalThis.__theoViteHotServer = server;
1240
+ server.ws.on("theo:devtools:request-manifest", () => {
1241
+ try {
1242
+ const tree = scanRoutes(ctx.appDir);
1243
+ broadcastRouteManifest(tree);
1244
+ } catch {
1245
+ }
1246
+ });
1247
+ const serverDir = resolve6(ctx.projectRoot, "server");
1248
+ server.middlewares.use(
1249
+ createActionMiddleware(server, serverDir, {
1250
+ pluginRunner: ctx.pluginRunner,
1251
+ csrfMode: ctx.csrfMode,
1252
+ disallowed: ctx.disallowed
1060
1253
  })
1061
- ]).optional(),
1062
- /**
1063
- * Cache subsystem (caching-and-revalidation-plan).
1064
- * Default `undefined` keeps caching disabled (backward compatible).
1065
- * Pass `cache: {}` to opt in with defaults.
1066
- */
1067
- cache: cacheSchema.optional(),
1068
- /**
1069
- * Devtools overlay (Phase 0.4.0+ — see docs/plans/devtools-plan.md).
1070
- *
1071
- * - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.
1072
- * - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).
1073
- * - `{ ... }`: devtools enabled with explicit defaults (position, theme).
1074
- *
1075
- * Tree-shaken to noop in prod via the dual-export pattern in
1076
- * `packages/theo/src/devtools/index.ts` (EC-17 positive prod check).
1077
- */
1078
- devtools: z.union([
1079
- z.literal(false),
1080
- z.object({
1081
- position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional(),
1082
- theme: z.enum(["light", "dark", "system"]).optional()
1254
+ );
1255
+ const servicesProxyPrefixes = ctx.services && Object.keys(ctx.services).length > 0 ? Object.values(ctx.services).map((s) => s.proxy) : [];
1256
+ const csrfReadinessStore = ctx.csrfReadinessStoreOverride ?? new CsrfReadinessStore();
1257
+ server.middlewares.use(
1258
+ createApiMiddleware(server, serverDir, {
1259
+ rateLimitConfig: ctx.rateLimit,
1260
+ pluginRunner: ctx.pluginRunner,
1261
+ batching: ctx.resolvedBatching,
1262
+ transformer: ctx.transformer,
1263
+ csrfMode: ctx.csrfMode,
1264
+ securityHeaders: ctx.securityHeaders,
1265
+ disallowed: ctx.disallowed,
1266
+ cors: ctx.cors,
1267
+ auditLogger: ctx.auditLogger,
1268
+ servicesProxyPrefixes,
1269
+ csrfReadinessStore
1083
1270
  })
1084
- ]).optional(),
1085
- /**
1086
- * Informative list of deploy adapters the app supports. Does NOT
1087
- * trigger per-build translations — only the `--target` CLI flag does
1088
- * (EC-201 / ADR D2). If `config.adapters` includes a target NOT
1089
- * matching `--target`, `theokit build` emits a cross-reference note.
1090
- */
1091
- adapters: z.array(z.string()).optional(),
1092
- /**
1093
- * Extra package names to pre-bundle via Vite's `optimizeDeps.include`.
1094
- *
1095
- * Framework auto-includes `@theokit/ui` and `lucide-react` when present.
1096
- * Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`
1097
- * (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers
1098
- * installing `mermaid` for Mermaid SVG rendering — must declare those
1099
- * here so Vite can resolve the literal in dev mode without
1100
- * "Failed to resolve module specifier" errors. Production builds use
1101
- * tsup/esbuild bundling and are not affected.
1102
- *
1103
- * Example:
1104
- * viteOptimizeDeps: ['mermaid']
1105
- */
1106
- viteOptimizeDeps: z.array(z.string()).optional(),
1107
- /**
1108
- * Jobs backend (ADR D3 + T2.1). When configured, every request gets
1109
- * `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a
1110
- * `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).
1111
- */
1112
- jobs: z.object({
1113
- backend: z.custom((v) => typeof v === "object" && v !== null)
1114
- }).optional(),
1115
- /**
1116
- * StorageManager configuration (T1.1 / ADR-0007).
1117
- *
1118
- * Declares Postgres servers + databases + Redis servers. Consumed by
1119
- * `getStorageManager().configure()` at boot via `start.ts`.
1120
- *
1121
- * Default `undefined` means manager exists but is not configured — adapters
1122
- * relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will
1123
- * throw with an actionable error on first use.
1124
- */
1125
- storage: storageSchema.optional(),
1126
- /**
1127
- * Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).
1128
- *
1129
- * Declarative external sidecar processes (Python FastAPI / Node Hono)
1130
- * that boot alongside the TheoKit TS app. Empty `services: {}` is the
1131
- * default and preserves Wave 1 behavior.
1132
- */
1133
- services: servicesConfigSchema,
1134
- /**
1135
- * G2 — OpenAPI emit (build-time only).
1136
- *
1137
- * When present, `theokit build` writes a generated `openapi.json` from
1138
- * every `defineRoute()` body/query/params Zod schema. `undefined` keeps
1139
- * the framework backward-compatible (no emit). Pass `openapi: {}` to
1140
- * opt in with defaults.
1141
- *
1142
- * Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
1143
- * "TheoKit App" · version "0.0.0" · outDir ".theo" (next to dist).
1144
- *
1145
- * The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
1146
- * `servers[].url` at emit time without rebuilding the config.
1147
- */
1148
- openapi: z.object({
1149
- servers: z.array(
1150
- z.object({
1151
- url: z.string().url(),
1152
- description: z.string().optional()
1153
- })
1154
- ).default([{ url: "http://localhost:3000", description: "Local development" }]),
1155
- specVersion: z.enum(["3.1.0", "3.0.3"]).default("3.1.0"),
1156
- title: z.string().default("TheoKit App"),
1157
- version: z.string().default("0.0.0"),
1158
- outDir: z.string().default(".theo")
1159
- }).optional()
1160
- }).refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {
1161
- message: "service.port collides with TheoKit web port \u2014 change one of them",
1162
- path: ["services"]
1163
- });
1164
-
1165
- // src/config/load-config.ts
1166
- var CONFIG_FILE = "theo.config.ts";
1167
- function deepMerge(base, override) {
1168
- const result = { ...base };
1169
- for (const key of Object.keys(override)) {
1170
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
1171
- const baseVal = base[key];
1172
- const overVal = override[key];
1173
- if (overVal !== null && typeof overVal === "object" && !Array.isArray(overVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
1174
- result[key] = deepMerge(
1175
- baseVal,
1176
- overVal
1271
+ );
1272
+ const configPath = resolve6(ctx.projectRoot, "theo.config.ts");
1273
+ server.watcher.on("change", (file) => {
1274
+ if (file === configPath) {
1275
+ console.warn(
1276
+ "\n[theokit] theo.config.ts changed; restart dev server for plugin changes to take effect\n"
1177
1277
  );
1178
- } else {
1179
- result[key] = overVal;
1180
1278
  }
1279
+ });
1280
+ if (ctx.ssrEnabled) {
1281
+ setupSsrDevMiddleware(server, {
1282
+ projectRoot: ctx.projectRoot,
1283
+ virtualEntryServerId: ctx.virtualEntryServerId,
1284
+ securityHeaders: ctx.securityHeaders
1285
+ });
1181
1286
  }
1182
- return result;
1183
- }
1184
- async function loadConfig(dir) {
1185
- loadEnv({ cwd: dir });
1186
- const configPath = resolve2(dir, CONFIG_FILE);
1187
- if (!existsSync2(configPath)) {
1188
- return theoConfigSchema.parse({});
1287
+ function handleRouteChange(filePath) {
1288
+ if (!isRouteFile(basename(filePath))) return;
1289
+ if (!filePath.startsWith(ctx.appDir)) return;
1290
+ const mod = server.moduleGraph.getModuleById(ctx.resolvedManifestId);
1291
+ if (mod) {
1292
+ server.moduleGraph.invalidateModule(mod);
1293
+ server.ws.send({ type: "full-reload" });
1294
+ }
1189
1295
  }
1190
- let mod;
1296
+ server.watcher.on("add", handleRouteChange);
1297
+ server.watcher.on("unlink", handleRouteChange);
1298
+ if (ctx.resolvedOpenApi !== void 0) {
1299
+ const openApiCfg = ctx.resolvedOpenApi;
1300
+ const { reEmitOpenApi } = await import("./dev-emit-5VPRCHOD.js");
1301
+ const openApiServerDir = resolve6(ctx.projectRoot, "server");
1302
+ const openApiDistDir = resolve6(ctx.projectRoot, ctx.resolvedDistDir);
1303
+ const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
1304
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
1305
+ server.watcher.on("change", (file) => {
1306
+ if (isRouteFileForOpenApi(file)) {
1307
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
1308
+ }
1309
+ });
1310
+ server.watcher.on("add", (file) => {
1311
+ if (isRouteFileForOpenApi(file)) {
1312
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
1313
+ }
1314
+ });
1315
+ server.watcher.on("unlink", (file) => {
1316
+ if (isRouteFileForOpenApi(file)) {
1317
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
1318
+ }
1319
+ });
1320
+ }
1321
+ setupWsUpgrade(server, ctx.projectRoot);
1322
+ server.httpServer?.once("close", () => {
1323
+ ;
1324
+ globalThis.__theoViteHotServer = void 0;
1325
+ });
1326
+ }
1327
+
1328
+ // src/vite-plugin/integrate-ui.ts
1329
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1330
+ import { dirname as dirname3, join as join4 } from "path";
1331
+ import { pathToFileURL } from "url";
1332
+
1333
+ // src/vite-plugin/auto-detect.ts
1334
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1335
+ import { dirname as dirname2, join as join3 } from "path";
1336
+ function isDeclared(name, projectRoot) {
1191
1337
  try {
1192
- mod = await import(pathToFileURL(configPath).href);
1193
- } catch (err) {
1194
- throw new TheoConfigError([{ field: "_file", message: err.message }], configPath);
1338
+ const raw = readFileSync3(join3(projectRoot, "package.json"), "utf-8");
1339
+ const pkg = JSON.parse(raw);
1340
+ return Boolean(
1341
+ pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? pkg.peerDependencies?.[name]
1342
+ );
1343
+ } catch {
1344
+ return false;
1195
1345
  }
1196
- const userConfig = mod.default;
1197
- if (userConfig == null || typeof userConfig !== "object") {
1198
- throw new TheoConfigError(
1199
- [
1200
- {
1201
- field: "_export",
1202
- message: "theo.config.ts must use export default defineConfig({...})"
1346
+ }
1347
+ function resolvePackageJson(name, cwd) {
1348
+ let dir = cwd;
1349
+ for (let depth = 0; depth < 10; depth++) {
1350
+ const pkgJsonPath = join3(dir, "node_modules", ...name.split("/"), "package.json");
1351
+ if (existsSync5(pkgJsonPath)) {
1352
+ try {
1353
+ const raw = readFileSync3(pkgJsonPath, "utf-8");
1354
+ const pkg = JSON.parse(raw);
1355
+ if (pkg.name === name) {
1356
+ return { path: pkgJsonPath, version: pkg.version };
1203
1357
  }
1204
- ],
1205
- configPath
1206
- );
1358
+ } catch {
1359
+ }
1360
+ }
1361
+ const parent = dirname2(dir);
1362
+ if (parent === dir) break;
1363
+ dir = parent;
1364
+ }
1365
+ return null;
1366
+ }
1367
+ function fallbackProbe(name, cwd) {
1368
+ const candidates = ["index.mjs", "dist/index.mjs", "dist/index.js", "index.js"];
1369
+ let dir = cwd;
1370
+ for (let depth = 0; depth < 10; depth++) {
1371
+ const pkgDir = join3(dir, "node_modules", ...name.split("/"));
1372
+ for (const candidate of candidates) {
1373
+ const entry = join3(pkgDir, candidate);
1374
+ if (existsSync5(entry)) return { resolvedEntry: entry };
1375
+ }
1376
+ const parent = dirname2(dir);
1377
+ if (parent === dir) break;
1378
+ dir = parent;
1379
+ }
1380
+ return null;
1381
+ }
1382
+ function findOwningPackageJson(entry, expectedName) {
1383
+ let dir = dirname2(entry);
1384
+ for (let i = 0; i < 10; i++) {
1385
+ const candidate = join3(dir, "package.json");
1386
+ if (existsSync5(candidate)) {
1387
+ try {
1388
+ const raw = readFileSync3(candidate, "utf-8");
1389
+ const pkg = JSON.parse(raw);
1390
+ if (pkg.name === expectedName) {
1391
+ return { path: candidate, version: pkg.version };
1392
+ }
1393
+ } catch {
1394
+ }
1395
+ }
1396
+ const parent = dirname2(dir);
1397
+ if (parent === dir) break;
1398
+ dir = parent;
1399
+ }
1400
+ return null;
1401
+ }
1402
+ function detectPackage(name, cwd) {
1403
+ if (!isDeclared(name, cwd)) {
1404
+ return { installed: false };
1405
+ }
1406
+ const pkgJson = resolvePackageJson(name, cwd);
1407
+ if (pkgJson) {
1408
+ return {
1409
+ installed: true,
1410
+ version: pkgJson.version,
1411
+ resolvedPath: pkgJson.path
1412
+ };
1413
+ }
1414
+ let dir = cwd;
1415
+ for (let i = 0; i < 10; i++) {
1416
+ const nmPath = join3(dir, "node_modules", ...name.split("/"));
1417
+ const nmPkgJson = join3(nmPath, "package.json");
1418
+ if (existsSync5(nmPkgJson)) {
1419
+ try {
1420
+ const raw = readFileSync3(nmPkgJson, "utf-8");
1421
+ const pkg = JSON.parse(raw);
1422
+ if (pkg.name === name) {
1423
+ return { installed: true, version: pkg.version, resolvedPath: nmPkgJson };
1424
+ }
1425
+ } catch {
1426
+ }
1427
+ return { installed: true };
1428
+ }
1429
+ const parent = dirname2(dir);
1430
+ if (parent === dir) break;
1431
+ dir = parent;
1432
+ }
1433
+ const probe = fallbackProbe(name, cwd);
1434
+ if (probe) {
1435
+ const owning = findOwningPackageJson(probe.resolvedEntry, name);
1436
+ if (owning) {
1437
+ return { installed: true, version: owning.version, resolvedPath: owning.path };
1438
+ }
1439
+ return { installed: true };
1207
1440
  }
1208
- let rawConfig = userConfig;
1209
- const nodeEnv = process.env.NODE_ENV;
1210
- if (nodeEnv) {
1211
- const envFile = `theo.config.${nodeEnv}.ts`;
1212
- const envPath = resolve2(dir, envFile);
1213
- if (existsSync2(envPath)) {
1441
+ return { installed: false };
1442
+ }
1443
+
1444
+ // src/vite-plugin/integrate-ui.ts
1445
+ function resolveConsumerImport(name, cwd) {
1446
+ let dir = cwd;
1447
+ for (let i = 0; i < 10; i++) {
1448
+ const pkgDir = join4(dir, "node_modules", ...name.split("/"));
1449
+ const pkgJsonPath = join4(pkgDir, "package.json");
1450
+ if (existsSync6(pkgJsonPath)) {
1214
1451
  try {
1215
- const envMod = await import(pathToFileURL(envPath).href);
1216
- const envConfig = envMod.default;
1217
- if (envConfig != null && typeof envConfig === "object") {
1218
- rawConfig = deepMerge(rawConfig, envConfig);
1452
+ const raw = readFileSync4(pkgJsonPath, "utf-8");
1453
+ const pkg = JSON.parse(raw);
1454
+ let entry;
1455
+ const dotExport = pkg.exports?.["."];
1456
+ if (typeof dotExport === "string") entry = dotExport;
1457
+ else if (dotExport && typeof dotExport === "object") {
1458
+ entry = dotExport.import ?? dotExport.default;
1219
1459
  }
1220
- } catch (err) {
1221
- throw new TheoConfigError([{ field: "_file", message: err.message }], envPath);
1460
+ entry ??= pkg.module ?? pkg.main;
1461
+ if (!entry) return null;
1462
+ const cleaned = entry.replace(/^\.\//, "");
1463
+ return pathToFileURL(join4(pkgDir, cleaned)).href;
1464
+ } catch {
1465
+ return null;
1222
1466
  }
1223
1467
  }
1468
+ const parent = dirname3(dir);
1469
+ if (parent === dir) break;
1470
+ dir = parent;
1224
1471
  }
1225
- const result = theoConfigSchema.safeParse(rawConfig);
1226
- if (!result.success) {
1227
- const issues = result.error.issues.map((i) => ({
1228
- field: i.path.join("."),
1229
- message: i.message
1230
- }));
1231
- throw new TheoConfigError(issues, configPath);
1232
- }
1233
- return result.data;
1472
+ return null;
1234
1473
  }
1235
-
1236
- // src/vite-plugin/theoui-detect.ts
1237
- import { existsSync as existsSync3, readFileSync } from "fs";
1238
- import { dirname, join as join2 } from "path";
1239
- function resolveTheoUiConfig(raw) {
1240
- return {
1241
- theme: raw?.theme ?? "violet-forge",
1242
- fonts: raw?.fonts ?? "bundled"
1243
- };
1474
+ function isValidPlugin(value) {
1475
+ if (value === null || typeof value !== "object") return false;
1476
+ if (Array.isArray(value)) return false;
1477
+ if (!("name" in value)) return false;
1478
+ return typeof value.name === "string";
1244
1479
  }
1245
- function resolveExportSubpath(pkgRoot, subpath) {
1246
- const pkgJsonPath = join2(pkgRoot, "package.json");
1247
- if (!existsSync3(pkgJsonPath)) return null;
1248
- try {
1249
- const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
1250
- const exportKey = `./${subpath}`;
1251
- const exp = pkg.exports?.[exportKey];
1252
- if (!exp) {
1253
- const fallback = join2(pkgRoot, "dist", subpath);
1254
- return existsSync3(fallback) ? fallback : null;
1255
- }
1256
- let target;
1257
- if (typeof exp === "string") target = exp;
1258
- else if (typeof exp === "object") {
1259
- const e = exp;
1260
- target = e.import ?? e.default;
1480
+ function normalizePluginReturn(value) {
1481
+ if (Array.isArray(value)) {
1482
+ const result = [];
1483
+ for (const entry of value) {
1484
+ if (!isValidPlugin(entry)) return null;
1485
+ result.push(entry);
1261
1486
  }
1262
- if (!target) return null;
1263
- const cleaned = target.replace(/^\.\//, "");
1264
- const candidate = join2(pkgRoot, cleaned);
1265
- return existsSync3(candidate) ? candidate : null;
1266
- } catch {
1267
- return null;
1487
+ return result.length > 0 ? result : null;
1268
1488
  }
1489
+ if (isValidPlugin(value)) return [value];
1490
+ return null;
1269
1491
  }
1270
- var defaultResolver = (specifier, projectRoot) => {
1271
- const parts = specifier.split("/");
1272
- const pkgName = parts[0]?.startsWith("@") && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
1273
- if (!pkgName) return false;
1274
- const subpath = specifier.slice(pkgName.length + 1);
1275
- if (!subpath) return false;
1276
- let dir = projectRoot;
1277
- for (let depth = 0; depth < 10; depth++) {
1278
- const pkgRoot = join2(dir, "node_modules", ...pkgName.split("/"));
1279
- if (existsSync3(pkgRoot)) {
1280
- const resolved = resolveExportSubpath(pkgRoot, subpath);
1281
- if (resolved) return true;
1282
- }
1283
- const parent = dirname(dir);
1284
- if (parent === dir) break;
1285
- dir = parent;
1492
+ async function integrateUseTheoUI(cwd, opts) {
1493
+ if (opts?.enabled === false) return [];
1494
+ const uiDetect = detectPackage("@theokit/ui", cwd);
1495
+ if (!uiDetect.installed) return [];
1496
+ if (opts?.consumerTailwindConfig || opts?.consumerPostcssConfig) {
1497
+ console.info(
1498
+ `[theokit] Detected your tailwind.config / postcss.config \u2014 skipping auto-config. Extend with \`import preset from '@theokit/ui/preset'\` to apply UI theme.`
1499
+ );
1500
+ return [];
1286
1501
  }
1287
- return false;
1288
- };
1289
- function isDeclaredInPackageJson(projectRoot) {
1502
+ const tailwindDetect = detectPackage("@tailwindcss/vite", cwd);
1503
+ if (!tailwindDetect.installed) {
1504
+ console.warn(
1505
+ `[theokit] @theokit/ui detected but @tailwindcss/vite is not installed. Run \`pnpm add -D @tailwindcss/vite\` to enable styling.`
1506
+ );
1507
+ return [];
1508
+ }
1509
+ const tailwindUrl = resolveConsumerImport("@tailwindcss/vite", cwd);
1510
+ if (!tailwindUrl) {
1511
+ console.warn(
1512
+ `[theokit] @tailwindcss/vite installed but its package entry was not resolvable from ${cwd}. Skipping auto-config.`
1513
+ );
1514
+ return [];
1515
+ }
1516
+ let tailwindMod;
1517
+ let uiMod;
1290
1518
  try {
1291
- const raw = readFileSync(join2(projectRoot, "package.json"), "utf-8");
1292
- const pkg = JSON.parse(raw);
1293
- return Boolean(
1294
- pkg.dependencies?.["@theokit/ui"] ?? pkg.devDependencies?.["@theokit/ui"] ?? pkg.peerDependencies?.["@theokit/ui"]
1519
+ tailwindMod = await import(tailwindUrl);
1520
+ } catch (err) {
1521
+ console.warn(
1522
+ `[theokit] @tailwindcss/vite dynamic import failed (${err.message}). Skipping auto-config.`
1295
1523
  );
1296
- } catch {
1297
- return false;
1524
+ return [];
1298
1525
  }
1299
- }
1300
- function detectTheoUi(projectRoot, rawConfig, resolver = defaultResolver) {
1301
- const config = resolveTheoUiConfig(typeof rawConfig === "object" ? rawConfig : void 0);
1302
- if (rawConfig === false) {
1303
- return { enabled: false, config };
1526
+ const uiPkgInstalled = detectPackage("@theokit/ui", cwd);
1527
+ const uiPkgDir = uiPkgInstalled.resolvedPath ? uiPkgInstalled.resolvedPath.replace(/[\\/]package\.json$/, "") : null;
1528
+ const uiPluginPath = uiPkgDir ? join4(uiPkgDir, "dist", "vite-plugin.js") : null;
1529
+ if (!uiPluginPath || !existsSync6(uiPluginPath)) {
1530
+ console.warn(
1531
+ `[theokit] @theokit/ui/vite-plugin entry not found at ${String(uiPluginPath)}. Skipping auto-config.`
1532
+ );
1533
+ return [];
1304
1534
  }
1305
- if (!isDeclaredInPackageJson(projectRoot)) {
1306
- return { enabled: false, config };
1535
+ try {
1536
+ uiMod = await import(pathToFileURL(uiPluginPath).href);
1537
+ } catch (err) {
1538
+ console.warn(
1539
+ `[theokit] @theokit/ui/vite-plugin dynamic import failed (${err.message}). Skipping auto-config.`
1540
+ );
1541
+ return [];
1307
1542
  }
1308
- const probes = ["@theokit/ui/styles.css", "@theokit/ui/fonts.css"];
1309
- for (const probe of probes) {
1310
- if (resolver(probe, projectRoot)) {
1311
- return { enabled: true, config };
1312
- }
1543
+ if (typeof tailwindMod.default !== "function") {
1544
+ console.warn(
1545
+ `[theokit] @tailwindcss/vite does not expose a default-export function. Skipping auto-config.`
1546
+ );
1547
+ return [];
1313
1548
  }
1314
- return { enabled: false, config };
1549
+ if (typeof uiMod.default !== "function") {
1550
+ console.warn(
1551
+ `[theokit] @theokit/ui/vite-plugin does not expose a default-export function. Skipping auto-config.`
1552
+ );
1553
+ return [];
1554
+ }
1555
+ let tailwindPlugin;
1556
+ let uiPlugin;
1557
+ try {
1558
+ tailwindPlugin = tailwindMod.default();
1559
+ } catch (err) {
1560
+ console.warn(`[theokit] @tailwindcss/vite() threw: ${err.message}`);
1561
+ return [];
1562
+ }
1563
+ try {
1564
+ uiPlugin = uiMod.default({
1565
+ tailwind: false
1566
+ });
1567
+ } catch (err) {
1568
+ console.warn(`[theokit] @theokit/ui/vite-plugin() threw: ${err.message}`);
1569
+ return [];
1570
+ }
1571
+ const tailwindPlugins = normalizePluginReturn(tailwindPlugin);
1572
+ if (tailwindPlugins === null) {
1573
+ console.warn(`[theokit] @tailwindcss/vite returned unexpected shape. Skipping auto-config.`);
1574
+ return [];
1575
+ }
1576
+ const uiPlugins = normalizePluginReturn(uiPlugin);
1577
+ if (uiPlugins === null) {
1578
+ console.warn(
1579
+ `[theokit] @theokit/ui/vite-plugin returned unexpected shape. Skipping auto-config.`
1580
+ );
1581
+ return [];
1582
+ }
1583
+ return [...tailwindPlugins, ...uiPlugins];
1315
1584
  }
1316
1585
 
1317
- // src/vite-plugin/config-resolve.ts
1318
- var DEFAULT_RESOLVED = {
1319
- pluginRunner: void 0,
1320
- transformer: void 0,
1321
- resolvedBatching: void 0,
1322
- theoUi: void 0,
1323
- csrfMode: "strict",
1324
- securityHeaders: void 0,
1325
- disallowed: void 0,
1326
- cors: void 0,
1327
- auditLogger: void 0,
1328
- devtoolsEnabled: true,
1329
- openapi: void 0,
1330
- distDir: ".theo"
1331
- };
1332
- async function resolvePluginConfig(projectRoot) {
1333
- try {
1334
- const userConfig = await loadConfig(projectRoot);
1335
- const pluginRunner = await createPluginRunnerFromConfig(userConfig.plugins);
1336
- const transformer = resolveTransformer(userConfig.serialization);
1337
- let resolvedBatching;
1338
- if (userConfig.batching === true) {
1339
- resolvedBatching = {};
1340
- } else if (typeof userConfig.batching === "object") {
1341
- resolvedBatching = userConfig.batching;
1342
- }
1343
- const theoUi = detectTheoUi(projectRoot, userConfig.ui);
1344
- let auditLogger;
1345
- const maybeLogger = userConfig.audit?.logger;
1346
- if (maybeLogger && typeof maybeLogger.log === "function") {
1347
- auditLogger = maybeLogger;
1348
- }
1349
- return {
1350
- pluginRunner,
1351
- transformer,
1352
- resolvedBatching,
1353
- theoUi,
1354
- csrfMode: userConfig.security?.csrf ?? "strict",
1355
- securityHeaders: userConfig.security?.headers,
1356
- disallowed: userConfig.security?.disallowed,
1357
- cors: userConfig.security?.cors,
1358
- auditLogger,
1359
- devtoolsEnabled: userConfig.devtools !== false,
1360
- openapi: userConfig.openapi,
1361
- distDir: userConfig.distDir
1362
- };
1363
- } catch {
1364
- return DEFAULT_RESOLVED;
1586
+ // src/vite-plugin/resolve-theo-root.ts
1587
+ import { existsSync as existsSync7 } from "fs";
1588
+ import { resolve as resolve7 } from "path";
1589
+ function resolveTheoRootDir(currentDir) {
1590
+ if (existsSync7(resolve7(currentDir, "client"))) {
1591
+ return currentDir;
1365
1592
  }
1593
+ return resolve7(currentDir, "..");
1366
1594
  }
1367
1595
 
1596
+ // src/vite-plugin/transform-html-hook.ts
1597
+ import { existsSync as existsSync8 } from "fs";
1598
+ import { resolve as resolve8 } from "path";
1599
+
1368
1600
  // src/vite-plugin/inject-devtools.ts
1369
1601
  var DEVTOOLS_VIRTUAL_ID = "/@theo/devtools/entry.js";
1370
1602
  var DEVTOOLS_RESOLVED_ID = "\0@theo/devtools/entry.js";
@@ -1450,385 +1682,461 @@ function injectStylesheets(html, opts) {
1450
1682
  ${html}`, injected: tagsToInject };
1451
1683
  }
1452
1684
 
1453
- // src/vite-plugin/integrate-ui.ts
1454
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1455
- import { dirname as dirname3, join as join4 } from "path";
1456
- import { pathToFileURL as pathToFileURL2 } from "url";
1685
+ // src/vite-plugin/transform-html-hook.ts
1686
+ function runTransformIndexHtml(html, ctx) {
1687
+ const entry = injectEntryClient(html);
1688
+ if (entry.warning) console.warn(entry.warning);
1689
+ let next = entry.html;
1690
+ const devtools = injectDevtoolsScript(next, {
1691
+ isDev: ctx.isDevMode.value,
1692
+ enabled: ctx.devtoolsEnabled
1693
+ });
1694
+ if (devtools.warning) console.warn(devtools.warning);
1695
+ next = devtools.html;
1696
+ const styles = injectStylesheets(next, {
1697
+ isDev: ctx.isDevMode.value,
1698
+ hasPackage: (name) => existsSync8(resolve8(ctx.projectRoot, "node_modules", ...name.split("/")))
1699
+ });
1700
+ next = styles.html;
1701
+ return next;
1702
+ }
1457
1703
 
1458
- // src/vite-plugin/auto-detect.ts
1459
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1460
- import { dirname as dirname2, join as join3 } from "path";
1461
- function isDeclared(name, projectRoot) {
1462
- try {
1463
- const raw = readFileSync2(join3(projectRoot, "package.json"), "utf-8");
1464
- const pkg = JSON.parse(raw);
1465
- return Boolean(
1466
- pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? pkg.peerDependencies?.[name]
1467
- );
1468
- } catch {
1469
- return false;
1704
+ // src/router/entry-server.ts
1705
+ function generateEntryServer(options = {}) {
1706
+ if (options.streaming) {
1707
+ return generateStreamingEntry(options);
1470
1708
  }
1709
+ return generateSingleShotEntry(options);
1471
1710
  }
1472
- function resolvePackageJson(name, cwd) {
1473
- let dir = cwd;
1474
- for (let depth = 0; depth < 10; depth++) {
1475
- const pkgJsonPath = join3(dir, "node_modules", ...name.split("/"), "package.json");
1476
- if (existsSync4(pkgJsonPath)) {
1477
- try {
1478
- const raw = readFileSync2(pkgJsonPath, "utf-8");
1479
- const pkg = JSON.parse(raw);
1480
- if (pkg.name === name) {
1481
- return { path: pkgJsonPath, version: pkg.version };
1482
- }
1483
- } catch {
1484
- }
1485
- }
1486
- const parent = dirname2(dir);
1487
- if (parent === dir) break;
1488
- dir = parent;
1711
+ function buildAppTreeJs(options) {
1712
+ const theme = options.theoUi?.theme ?? "violet-forge";
1713
+ if (options.theoUi) {
1714
+ return [
1715
+ `React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },`,
1716
+ ` React.createElement(Suspense, { fallback: null },`,
1717
+ ` React.createElement(StaticRouterProvider, { router, context, hydrate: false })`,
1718
+ ` )`,
1719
+ ` )`
1720
+ ].join("\n");
1489
1721
  }
1490
- return null;
1722
+ return `React.createElement(Suspense, { fallback: null },
1723
+ React.createElement(StaticRouterProvider, { router, context, hydrate: false })
1724
+ )`;
1491
1725
  }
1492
- function fallbackProbe(name, cwd) {
1493
- const candidates = ["index.mjs", "dist/index.mjs", "dist/index.js", "index.js"];
1494
- let dir = cwd;
1495
- for (let depth = 0; depth < 10; depth++) {
1496
- const pkgDir = join3(dir, "node_modules", ...name.split("/"));
1497
- for (const candidate of candidates) {
1498
- const entry = join3(pkgDir, candidate);
1499
- if (existsSync4(entry)) return { resolvedEntry: entry };
1500
- }
1501
- const parent = dirname2(dir);
1502
- if (parent === dir) break;
1503
- dir = parent;
1504
- }
1505
- return null;
1726
+ function hydrationDataExtractSnippet() {
1727
+ return [
1728
+ ` const hydrationData = {`,
1729
+ ` loaderData: context.loaderData,`,
1730
+ ` actionData: context.actionData,`,
1731
+ ` errors: context.errors,`,
1732
+ ` }`
1733
+ ].join("\n");
1506
1734
  }
1507
- function findOwningPackageJson(entry, expectedName) {
1508
- let dir = dirname2(entry);
1509
- for (let i = 0; i < 10; i++) {
1510
- const candidate = join3(dir, "package.json");
1511
- if (existsSync4(candidate)) {
1512
- try {
1513
- const raw = readFileSync2(candidate, "utf-8");
1514
- const pkg = JSON.parse(raw);
1515
- if (pkg.name === expectedName) {
1516
- return { path: candidate, version: pkg.version };
1517
- }
1518
- } catch {
1519
- }
1520
- }
1521
- const parent = dirname2(dir);
1522
- if (parent === dir) break;
1523
- dir = parent;
1524
- }
1525
- return null;
1735
+ function generateSingleShotEntry(options) {
1736
+ const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
1737
+ ` : "";
1738
+ const appTree = buildAppTreeJs(options);
1739
+ return [
1740
+ `import React, { Suspense } from 'react'`,
1741
+ `import { renderToPipeableStream } from 'react-dom/server'`,
1742
+ `import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
1743
+ `import { PassThrough } from 'node:stream'`,
1744
+ `import { routes } from '/@theo/route-manifest'`,
1745
+ theoUiImport,
1746
+ `export async function render(url, options = {}) {`,
1747
+ ` const handler = createStaticHandler(routes)`,
1748
+ ` const request = new Request('http://localhost' + url)`,
1749
+ ` const context = await handler.query(request)`,
1750
+ ``,
1751
+ ` if (context instanceof Response) {`,
1752
+ ` return { redirect: context }`,
1753
+ ` }`,
1754
+ ``,
1755
+ ` const router = createStaticRouter(handler.dataRoutes, context)`,
1756
+ ` const app = ${appTree}`,
1757
+ ``,
1758
+ hydrationDataExtractSnippet(),
1759
+ ``,
1760
+ ` return new Promise((resolve, reject) => {`,
1761
+ ` let html = ''`,
1762
+ ` let piped = false`,
1763
+ ` const passthrough = new PassThrough()`,
1764
+ ` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
1765
+ ` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
1766
+ ` passthrough.on('error', reject)`,
1767
+ ``,
1768
+ ` // Pipe on onShellReady (Next.js pattern). Calling pipe() twice`,
1769
+ ` // throws "React currently only supports piping to one writable`,
1770
+ ` // stream". The \`piped\` flag is a belt-and-suspenders guard if`,
1771
+ ` // onShellReady fires unexpectedly more than once.`,
1772
+ ` // Forward options.nonce to React so every <script> tag React`,
1773
+ ` // emits (Suspense boundary scripts) carries the nonce attribute.`,
1774
+ ` const { pipe } = renderToPipeableStream(app, {`,
1775
+ ` nonce: options.nonce,`,
1776
+ ` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
1777
+ ` onShellError(err) { reject(err) },`,
1778
+ ` onError(err) { console.error('[SSR Error]', err) },`,
1779
+ ` })`,
1780
+ ` })`,
1781
+ `}`
1782
+ ].join("\n");
1783
+ }
1784
+ function streamingWebRenderer(appTree) {
1785
+ return [
1786
+ `// T2.3 \u2014 Web Standards streaming entry for edge runtimes (Cloudflare,`,
1787
+ `// Bun, Deno, Vercel Edge). Uses renderToReadableStream and returns a`,
1788
+ `// Response with the stream as body. Honors request.signal for client`,
1789
+ `// disconnect cleanup.`,
1790
+ `export async function renderStreamingWeb(request, options = {}) {`,
1791
+ ` const handler = createStaticHandler(routes)`,
1792
+ ` const url = new URL(request.url)`,
1793
+ ` const context = await handler.query(request)`,
1794
+ ``,
1795
+ ` if (context instanceof Response) {`,
1796
+ ` return context`,
1797
+ ` }`,
1798
+ ``,
1799
+ ` const router = createStaticRouter(handler.dataRoutes, context)`,
1800
+ ` const app = ${appTree}`,
1801
+ ``,
1802
+ ` const stream = await renderToReadableStream(app, {`,
1803
+ ` signal: request.signal,`,
1804
+ ` nonce: options.nonce,`,
1805
+ ` onError(err) { console.error('[SSR Web Stream Error]', err) },`,
1806
+ ` })`,
1807
+ ` return new Response(stream, {`,
1808
+ ` status: 200,`,
1809
+ ` headers: {`,
1810
+ ` 'Content-Type': 'text/html; charset=utf-8',`,
1811
+ ` 'Transfer-Encoding': 'chunked',`,
1812
+ ` },`,
1813
+ ` })`,
1814
+ `}`
1815
+ ];
1816
+ }
1817
+ function streamingNodeRenderer(appTree) {
1818
+ return [
1819
+ `// T6.1 \u2014 Node streaming SSR entry (opt-in via theo.config.ts > ssrStreaming: true)`,
1820
+ `// Flushes the shell as soon as it's ready, then streams Suspense boundaries.`,
1821
+ `// EC-11: respects request.signal for client-disconnect cleanup.`,
1822
+ `export async function renderStreaming(url, response, options = {}) {`,
1823
+ ` const handler = createStaticHandler(routes)`,
1824
+ ` const request = new Request('http://localhost' + url, { signal: options.signal })`,
1825
+ ` const context = await handler.query(request)`,
1826
+ ``,
1827
+ ` if (context instanceof Response) {`,
1828
+ ` return { redirect: context }`,
1829
+ ` }`,
1830
+ ``,
1831
+ ` const router = createStaticRouter(handler.dataRoutes, context)`,
1832
+ ` const app = ${appTree}`,
1833
+ ``,
1834
+ ` return new Promise((resolve, reject) => {`,
1835
+ ` let didError = false`,
1836
+ ` const stream = renderToPipeableStream(app, {`,
1837
+ ` nonce: options.nonce,`,
1838
+ ` onShellReady() {`,
1839
+ ` response.statusCode = didError ? 500 : 200`,
1840
+ ` response.setHeader('Content-Type', 'text/html; charset=utf-8')`,
1841
+ ` response.setHeader('Transfer-Encoding', 'chunked')`,
1842
+ ` stream.pipe(response)`,
1843
+ ` resolve({ streaming: true })`,
1844
+ ` },`,
1845
+ ` onShellError(err) { reject(err) },`,
1846
+ ` onError(err) {`,
1847
+ ` didError = true`,
1848
+ ` console.error('[SSR Stream Error]', err)`,
1849
+ ` },`,
1850
+ ` })`,
1851
+ ``,
1852
+ ` // EC-11: client disconnect cleanup`,
1853
+ ` if (options.signal) {`,
1854
+ ` options.signal.addEventListener('abort', () => { stream.abort() })`,
1855
+ ` }`,
1856
+ ` })`,
1857
+ `}`
1858
+ ];
1859
+ }
1860
+ function backCompatRenderer(appTree) {
1861
+ return [
1862
+ `// Backward compatibility: keep the single-shot render export available so`,
1863
+ `// callers that always used 'render()' don't break when streaming is on.`,
1864
+ `export async function render(url, options = {}) {`,
1865
+ ` const handler = createStaticHandler(routes)`,
1866
+ ` const request = new Request('http://localhost' + url)`,
1867
+ ` const context = await handler.query(request)`,
1868
+ ``,
1869
+ ` if (context instanceof Response) {`,
1870
+ ` return { redirect: context }`,
1871
+ ` }`,
1872
+ ``,
1873
+ ` const router = createStaticRouter(handler.dataRoutes, context)`,
1874
+ ` const app = ${appTree}`,
1875
+ ``,
1876
+ hydrationDataExtractSnippet(),
1877
+ ``,
1878
+ ` const { PassThrough } = await import('node:stream')`,
1879
+ ` return new Promise((resolve, reject) => {`,
1880
+ ` let html = ''`,
1881
+ ` let piped = false`,
1882
+ ` const passthrough = new PassThrough()`,
1883
+ ` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
1884
+ ` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
1885
+ ` passthrough.on('error', reject)`,
1886
+ ``,
1887
+ ` // Pipe on onShellReady (Next.js pattern). nonce forwarded.`,
1888
+ ` const { pipe } = renderToPipeableStream(app, {`,
1889
+ ` nonce: options.nonce,`,
1890
+ ` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
1891
+ ` onShellError(err) { reject(err) },`,
1892
+ ` onError(err) { console.error('[SSR Error]', err) },`,
1893
+ ` })`,
1894
+ ` })`,
1895
+ `}`
1896
+ ];
1897
+ }
1898
+ function generateStreamingEntry(options) {
1899
+ const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
1900
+ ` : "";
1901
+ const appTree = buildAppTreeJs(options);
1902
+ return [
1903
+ `import React, { Suspense } from 'react'`,
1904
+ `import { renderToPipeableStream, renderToReadableStream } from 'react-dom/server'`,
1905
+ `import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
1906
+ `import { routes } from '/@theo/route-manifest'`,
1907
+ theoUiImport,
1908
+ ``,
1909
+ ...streamingWebRenderer(appTree),
1910
+ ``,
1911
+ ...streamingNodeRenderer(appTree),
1912
+ ``,
1913
+ ...backCompatRenderer(appTree)
1914
+ ].join("\n");
1526
1915
  }
1527
- function detectPackage(name, cwd) {
1528
- if (!isDeclared(name, cwd)) {
1529
- return { installed: false };
1530
- }
1531
- const pkgJson = resolvePackageJson(name, cwd);
1532
- if (pkgJson) {
1533
- return {
1534
- installed: true,
1535
- version: pkgJson.version,
1536
- resolvedPath: pkgJson.path
1537
- };
1538
- }
1539
- let dir = cwd;
1540
- for (let i = 0; i < 10; i++) {
1541
- const nmPath = join3(dir, "node_modules", ...name.split("/"));
1542
- const nmPkgJson = join3(nmPath, "package.json");
1543
- if (existsSync4(nmPkgJson)) {
1544
- try {
1545
- const raw = readFileSync2(nmPkgJson, "utf-8");
1546
- const pkg = JSON.parse(raw);
1547
- if (pkg.name === name) {
1548
- return { installed: true, version: pkg.version, resolvedPath: nmPkgJson };
1549
- }
1550
- } catch {
1551
- }
1552
- return { installed: true };
1553
- }
1554
- const parent = dirname2(dir);
1555
- if (parent === dir) break;
1556
- dir = parent;
1557
- }
1558
- const probe = fallbackProbe(name, cwd);
1559
- if (probe) {
1560
- const owning = findOwningPackageJson(probe.resolvedEntry, name);
1561
- if (owning) {
1562
- return { installed: true, version: owning.version, resolvedPath: owning.path };
1563
- }
1564
- return { installed: true };
1916
+
1917
+ // src/router/entry.ts
1918
+ function generateEntryClient(ssr, opts = {}) {
1919
+ const rootMethod = ssr ? "hydrateRoot" : "createRoot";
1920
+ const theoUiImports = [];
1921
+ if (opts.theoUi) {
1922
+ theoUiImports.push(
1923
+ `// T2.2 \u2014 TheoUI CSS auto-injected (config.ui)`,
1924
+ `import '@theokit/ui/styles.css'`
1925
+ );
1926
+ const fontsModule = opts.theoUi.fonts === "cdn" ? "fonts-cdn.css" : "fonts.css";
1927
+ theoUiImports.push(
1928
+ `import '@theokit/ui/${fontsModule}'`,
1929
+ `// T2.3 \u2014 TheoUIProvider auto-wraps RouterProvider`,
1930
+ `import { TheoUIProvider } from '@theokit/ui'`
1931
+ );
1565
1932
  }
1566
- return { installed: false };
1933
+ const theme = opts.theoUi?.theme ?? "violet-forge";
1934
+ const routerTree = opts.theoUi ? `React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },
1935
+ React.createElement(Suspense, { fallback: null },
1936
+ React.createElement(RouterProvider, { router })
1937
+ )
1938
+ )` : `React.createElement(Suspense, { fallback: null },
1939
+ React.createElement(RouterProvider, { router })
1940
+ )`;
1941
+ const renderCall = ssr ? ` ${rootMethod}(el,
1942
+ ${routerTree}
1943
+ )` : ` ${rootMethod}(el).render(
1944
+ ${routerTree}
1945
+ )`;
1946
+ const hydrationLine = ssr ? `const router = createBrowserRouter(routes, { hydrationData: window.__staticRouterHydrationData })` : `const router = createBrowserRouter(routes)`;
1947
+ const preloadBlock = ssr ? [
1948
+ `// Phase 4 \u2014 preload matched-route modules before hydrate (EC-3 safeguard)`,
1949
+ `const __theoMatches = matchRoutes(routes, window.location.pathname) ?? []`,
1950
+ `const __theoPreloadPaths = __theoMatches`,
1951
+ ` .map((m) => m.route && (m.route).path)`,
1952
+ ` .filter((p) => typeof p === 'string' && p in __theoPreloadMap)`,
1953
+ `const __theoPreloadPromise = Promise.all(`,
1954
+ ` __theoPreloadPaths.map((p) => __theoPreloadMap[p]().catch((err) => { console.error('[theo] preload failed', p, err); return null }))`,
1955
+ `)`,
1956
+ `const __theoTimeout = new Promise((resolve) => setTimeout(() => resolve('timeout'), 1500))`,
1957
+ `await Promise.race([__theoPreloadPromise, __theoTimeout])`,
1958
+ ""
1959
+ ] : [];
1960
+ const renderBlock = ssr ? [
1961
+ `if (el) {`,
1962
+ ` ;(async () => {`,
1963
+ ...preloadBlock.map((l) => " " + l),
1964
+ renderCall,
1965
+ ` })()`,
1966
+ `}`
1967
+ ] : [`if (el) {`, renderCall, `}`];
1968
+ const manifestImports = ssr ? `import { routes, __theoPreloadMap } from '/@theo/route-manifest'` : `import { routes } from '/@theo/route-manifest'`;
1969
+ const reactRouterImports = ssr ? `import { createBrowserRouter, RouterProvider, matchRoutes } from 'react-router'` : `import { createBrowserRouter, RouterProvider } from 'react-router'`;
1970
+ return [
1971
+ `import React, { Suspense } from 'react'`,
1972
+ `import { ${rootMethod} } from 'react-dom/client'`,
1973
+ reactRouterImports,
1974
+ manifestImports,
1975
+ `// T1.3 \u2014 side-effect import sets globalThis.__THEO_TRANSFORMER__ for theoFetch`,
1976
+ `import '/@theo/runtime-config'`,
1977
+ ...theoUiImports,
1978
+ ``,
1979
+ hydrationLine,
1980
+ `const el = document.getElementById('root')`,
1981
+ ...renderBlock
1982
+ ].join("\n");
1567
1983
  }
1568
1984
 
1569
- // src/vite-plugin/integrate-ui.ts
1570
- function resolveConsumerImport(name, cwd) {
1571
- let dir = cwd;
1572
- for (let i = 0; i < 10; i++) {
1573
- const pkgDir = join4(dir, "node_modules", ...name.split("/"));
1574
- const pkgJsonPath = join4(pkgDir, "package.json");
1575
- if (existsSync5(pkgJsonPath)) {
1576
- try {
1577
- const raw = readFileSync3(pkgJsonPath, "utf-8");
1578
- const pkg = JSON.parse(raw);
1579
- let entry;
1580
- const dotExport = pkg.exports?.["."];
1581
- if (typeof dotExport === "string") entry = dotExport;
1582
- else if (dotExport && typeof dotExport === "object") {
1583
- entry = dotExport.import ?? dotExport.default;
1584
- }
1585
- entry ??= pkg.module ?? pkg.main;
1586
- if (!entry) return null;
1587
- const cleaned = entry.replace(/^\.\//, "");
1588
- return pathToFileURL2(join4(pkgDir, cleaned)).href;
1589
- } catch {
1590
- return null;
1591
- }
1592
- }
1593
- const parent = dirname3(dir);
1594
- if (parent === dir) break;
1595
- dir = parent;
1596
- }
1597
- return null;
1985
+ // src/router/generate.ts
1986
+ function normalizePath(p) {
1987
+ return p.replace(/\\/g, "/");
1598
1988
  }
1599
- function isValidPlugin(value) {
1600
- if (value === null || typeof value !== "object") return false;
1601
- if (Array.isArray(value)) return false;
1602
- if (!("name" in value)) return false;
1603
- return typeof value.name === "string";
1989
+ function safeVarName(segment, prefix) {
1990
+ const safe = segment.replace(/[^a-zA-Z0-9]/g, "_") || "root";
1991
+ return `${prefix}_${safe}`;
1604
1992
  }
1605
- function normalizePluginReturn(value) {
1606
- if (Array.isArray(value)) {
1607
- const result = [];
1608
- for (const entry of value) {
1609
- if (!isValidPlugin(entry)) return null;
1610
- result.push(entry);
1611
- }
1612
- return result.length > 0 ? result : null;
1613
- }
1614
- if (isValidPlugin(value)) return [value];
1615
- return null;
1993
+ function buildRoutePath(parents, segment) {
1994
+ const joined = [...parents, segment].filter(Boolean).join("/");
1995
+ return "/" + joined;
1616
1996
  }
1617
- async function integrateUseTheoUI(cwd, opts) {
1618
- if (opts?.enabled === false) return [];
1619
- const uiDetect = detectPackage("@theokit/ui", cwd);
1620
- if (!uiDetect.installed) return [];
1621
- if (opts?.consumerTailwindConfig || opts?.consumerPostcssConfig) {
1622
- console.info(
1623
- `[theokit] Detected your tailwind.config / postcss.config \u2014 skipping auto-config. Extend with \`import preset from '@theokit/ui/preset'\` to apply UI theme.`
1624
- );
1625
- return [];
1626
- }
1627
- const tailwindDetect = detectPackage("@tailwindcss/vite", cwd);
1628
- if (!tailwindDetect.installed) {
1629
- console.warn(
1630
- `[theokit] @theokit/ui detected but @tailwindcss/vite is not installed. Run \`pnpm add -D @tailwindcss/vite\` to enable styling.`
1631
- );
1632
- return [];
1633
- }
1634
- const tailwindUrl = resolveConsumerImport("@tailwindcss/vite", cwd);
1635
- if (!tailwindUrl) {
1636
- console.warn(
1637
- `[theokit] @tailwindcss/vite installed but its package entry was not resolvable from ${cwd}. Skipping auto-config.`
1638
- );
1639
- return [];
1640
- }
1641
- let tailwindMod;
1642
- let uiMod;
1643
- try {
1644
- tailwindMod = await import(tailwindUrl);
1645
- } catch (err) {
1646
- console.warn(
1647
- `[theokit] @tailwindcss/vite dynamic import failed (${err.message}). Skipping auto-config.`
1648
- );
1649
- return [];
1650
- }
1651
- const uiPkgInstalled = detectPackage("@theokit/ui", cwd);
1652
- const uiPkgDir = uiPkgInstalled.resolvedPath ? uiPkgInstalled.resolvedPath.replace(/[\\/]package\.json$/, "") : null;
1653
- const uiPluginPath = uiPkgDir ? join4(uiPkgDir, "dist", "vite-plugin.js") : null;
1654
- if (!uiPluginPath || !existsSync5(uiPluginPath)) {
1655
- console.warn(
1656
- `[theokit] @theokit/ui/vite-plugin entry not found at ${String(uiPluginPath)}. Skipping auto-config.`
1657
- );
1658
- return [];
1659
- }
1660
- try {
1661
- uiMod = await import(pathToFileURL2(uiPluginPath).href);
1662
- } catch (err) {
1663
- console.warn(
1664
- `[theokit] @theokit/ui/vite-plugin dynamic import failed (${err.message}). Skipping auto-config.`
1665
- );
1666
- return [];
1667
- }
1668
- if (typeof tailwindMod.default !== "function") {
1669
- console.warn(
1670
- `[theokit] @tailwindcss/vite does not expose a default-export function. Skipping auto-config.`
1671
- );
1672
- return [];
1997
+ function pushIf(staticImports, filePath, varName) {
1998
+ if (filePath !== void 0) {
1999
+ staticImports.push({ varName, importPath: normalizePath(filePath) });
1673
2000
  }
1674
- if (typeof uiMod.default !== "function") {
1675
- console.warn(
1676
- `[theokit] @theokit/ui/vite-plugin does not expose a default-export function. Skipping auto-config.`
1677
- );
1678
- return [];
2001
+ }
2002
+ function walkRouteTree(node, parents, acc) {
2003
+ const seg = node.segment || "root";
2004
+ const routePath = buildRoutePath(parents, node.segment);
2005
+ if (node.page !== void 0) {
2006
+ acc.lazyPages.push({
2007
+ varName: safeVarName(seg, "Page"),
2008
+ importPath: normalizePath(node.page),
2009
+ routePath
2010
+ });
1679
2011
  }
1680
- let tailwindPlugin;
1681
- let uiPlugin;
1682
- try {
1683
- tailwindPlugin = tailwindMod.default();
1684
- } catch (err) {
1685
- console.warn(`[theokit] @tailwindcss/vite() threw: ${err.message}`);
1686
- return [];
2012
+ if (node.layout !== void 0) acc.layoutState.found = true;
2013
+ pushIf(acc.staticImports, node.layout, safeVarName(seg, "Layout"));
2014
+ pushIf(acc.staticImports, node.error, safeVarName(seg, "Error"));
2015
+ pushIf(acc.staticImports, node.loading, safeVarName(seg, "Loading"));
2016
+ pushIf(acc.staticImports, node.notFound, safeVarName(seg, "NotFound"));
2017
+ for (const child of node.children) {
2018
+ const nextParents = node.segment ? [...parents, node.segment] : parents;
2019
+ walkRouteTree(child, nextParents, acc);
1687
2020
  }
1688
- try {
1689
- uiPlugin = uiMod.default({
1690
- tailwind: false
1691
- });
1692
- } catch (err) {
1693
- console.warn(`[theokit] @theokit/ui/vite-plugin() threw: ${err.message}`);
1694
- return [];
2021
+ }
2022
+ function generateRouteManifest(tree) {
2023
+ const acc = {
2024
+ staticImports: [],
2025
+ lazyPages: [],
2026
+ layoutState: { found: false }
2027
+ };
2028
+ walkRouteTree(tree, [], acc);
2029
+ const staticImports = acc.staticImports;
2030
+ const lazyPages = acc.lazyPages;
2031
+ const layoutState = acc.layoutState;
2032
+ const lines = [`import React, { Suspense } from 'react'`];
2033
+ if (layoutState.found) {
2034
+ lines.push(`import { Outlet } from 'react-router'`);
1695
2035
  }
1696
- const tailwindPlugins = normalizePluginReturn(tailwindPlugin);
1697
- if (tailwindPlugins === null) {
1698
- console.warn(`[theokit] @tailwindcss/vite returned unexpected shape. Skipping auto-config.`);
1699
- return [];
2036
+ lines.push("");
2037
+ for (const imp of staticImports) {
2038
+ lines.push(`import ${imp.varName} from '${imp.importPath}'`);
1700
2039
  }
1701
- const uiPlugins = normalizePluginReturn(uiPlugin);
1702
- if (uiPlugins === null) {
1703
- console.warn(
1704
- `[theokit] @theokit/ui/vite-plugin returned unexpected shape. Skipping auto-config.`
1705
- );
1706
- return [];
2040
+ for (const lp of lazyPages) {
2041
+ lines.push(`const ${lp.varName} = React.lazy(() => import('${lp.importPath}'))`);
1707
2042
  }
1708
- return [...tailwindPlugins, ...uiPlugins];
1709
- }
1710
-
1711
- // src/vite-plugin/resolve-theo-root.ts
1712
- import { existsSync as existsSync6 } from "fs";
1713
- import { resolve as resolve3 } from "path";
1714
- function resolveTheoRootDir(currentDir) {
1715
- if (existsSync6(resolve3(currentDir, "client"))) {
1716
- return currentDir;
2043
+ lines.push("");
2044
+ const preloadEntries = lazyPages.map(
2045
+ (lp) => ` '${lp.routePath}': () => import('${lp.importPath}'),`
2046
+ );
2047
+ lines.push("export const __theoPreloadMap = {");
2048
+ for (const e of preloadEntries) lines.push(e);
2049
+ lines.push("}", "");
2050
+ function buildChildrenArray(node, seg) {
2051
+ const childConfigs = [];
2052
+ if (node.page) {
2053
+ const pageVar = safeVarName(seg, "Page");
2054
+ const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
2055
+ const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
2056
+ childConfigs.push(`{ index: true, element: ${pageElement} }`);
2057
+ }
2058
+ for (const child of node.children) {
2059
+ childConfigs.push(genRouteConfig(child, false));
2060
+ }
2061
+ if (node.notFound) {
2062
+ const nfVar = safeVarName(seg, "NotFound");
2063
+ childConfigs.push(`{ path: '*', element: React.createElement(${nfVar}) }`);
2064
+ }
2065
+ let arr = `[${childConfigs.join(", ")}]`;
2066
+ if (node.error) {
2067
+ const errVar = safeVarName(seg, "Error");
2068
+ arr = `[{ errorElement: React.createElement(${errVar}), children: ${arr} }]`;
2069
+ }
2070
+ return arr;
1717
2071
  }
1718
- return resolve3(currentDir, "..");
1719
- }
1720
-
1721
- // src/vite-plugin/ssr-dev-middleware.ts
1722
- import { readFileSync as readFileSync4 } from "fs";
1723
- import { resolve as resolve4 } from "path";
1724
- function isSsrRenderResult(value) {
1725
- if (typeof value !== "object" || value === null) return false;
1726
- if (!("html" in value)) return false;
1727
- return typeof value.html === "string";
1728
- }
1729
- function setupSsrDevMiddleware(server, opts) {
1730
- server.middlewares.use((req, res, next) => {
1731
- void (async () => {
1732
- const url = req.url ?? "/";
1733
- if (url.startsWith("/api/") || url.startsWith("/@") || url.startsWith("/node_modules/") || url.includes(".")) {
1734
- next();
1735
- return;
1736
- }
1737
- try {
1738
- const indexPath = resolve4(opts.projectRoot, "index.html");
1739
- let template = readFileSync4(indexPath, "utf-8");
1740
- template = await server.transformIndexHtml(url, template);
1741
- const nonce = generateNonce();
1742
- applySecurityHeaders(
1743
- res,
1744
- opts.securityHeaders ?? {},
1745
- { production: process.env.NODE_ENV === "production" },
1746
- { nonce }
1747
- );
1748
- const mod = await server.ssrLoadModule(opts.virtualEntryServerId);
1749
- const result = await mod.render(url, { nonce });
1750
- if (result && typeof result === "object" && "redirect" in result) {
1751
- res.writeHead(302, {
1752
- Location: result.redirect.headers.get("location") ?? "/"
1753
- });
1754
- res.end();
1755
- return;
1756
- }
1757
- let ssrHtml;
1758
- let hydrationScript = "";
1759
- if (typeof result === "string") {
1760
- ssrHtml = result;
1761
- } else if (isSsrRenderResult(result)) {
1762
- ssrHtml = result.html;
1763
- const dataJson = JSON.stringify(result.hydrationData).replace(/</g, "\\u003c");
1764
- hydrationScript = `<script nonce="${nonce}">window.__staticRouterHydrationData=${dataJson}</script>`;
1765
- } else {
1766
- ssrHtml = "";
1767
- }
1768
- const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
1769
- if (!rootDivMatch) {
1770
- res.writeHead(200, { "Content-Type": "text/html" });
1771
- res.end(template);
1772
- return;
1773
- }
1774
- const splitIdx = template.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
1775
- const html = template.slice(0, splitIdx) + ssrHtml + hydrationScript + template.slice(splitIdx);
1776
- res.writeHead(200, { "Content-Type": "text/html" });
1777
- res.end(html);
1778
- } catch (err) {
1779
- server.ssrFixStacktrace(err);
1780
- console.error("[SSR Dev Error]", err);
1781
- next();
1782
- return;
2072
+ function genRouteConfig(node, isRoot) {
2073
+ const seg = node.segment || "root";
2074
+ const childrenArray = buildChildrenArray(node, seg);
2075
+ if (node.layout) {
2076
+ const layoutVar = safeVarName(seg, "Layout");
2077
+ const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
2078
+ return `{ ${pathPart}, element: React.createElement(${layoutVar}, { children: React.createElement(Outlet) }), children: ${childrenArray} }`;
2079
+ }
2080
+ if (isRoot) {
2081
+ if (node.children.length === 0 && !node.page && !node.notFound && !node.error) {
2082
+ return `{ path: '/', children: [] }`;
1783
2083
  }
1784
- })();
1785
- });
2084
+ return `{ path: '/', children: ${childrenArray} }`;
2085
+ }
2086
+ if (node.page && node.children.length === 0 && !node.error && !node.notFound) {
2087
+ const pageVar = safeVarName(seg, "Page");
2088
+ const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
2089
+ const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
2090
+ return `{ path: '${node.segment}', element: ${pageElement} }`;
2091
+ }
2092
+ return `{ path: '${node.segment}', children: ${childrenArray} }`;
2093
+ }
2094
+ const routeConfig = genRouteConfig(tree, true);
2095
+ lines.push(`export const routes = [${routeConfig}]`);
2096
+ return lines.join("\n");
1786
2097
  }
1787
2098
 
1788
- // src/vite-plugin/ws-upgrade.ts
1789
- import { resolve as resolve5 } from "path";
1790
- function setupWsUpgrade(server, projectRoot) {
1791
- const wsRoutes = scanWebSocketRoutes(resolve5(projectRoot, "server"));
1792
- if (wsRoutes.length === 0) return;
1793
- const wsHttpServer = server.httpServer;
1794
- if (!wsHttpServer) return;
1795
- void import("ws").then(({ WebSocketServer }) => {
1796
- const wss = new WebSocketServer({ noServer: true });
1797
- wsHttpServer.on("upgrade", (request, socket, head) => {
1798
- void (async () => {
1799
- const url = request.url ?? "/";
1800
- if (!url.startsWith("/ws/")) return;
1801
- const wsPath = url.split("?")[0];
1802
- const match = wsRoutes.find((r) => r.wsPath === wsPath);
1803
- if (!match) {
1804
- socket.destroy();
1805
- return;
1806
- }
1807
- try {
1808
- const mod = await server.ssrLoadModule(match.filePath);
1809
- const handler = mod.default ?? mod;
1810
- wss.handleUpgrade(request, socket, head, (ws) => {
1811
- void handler.onOpen?.(ws, request);
1812
- ws.on("message", (data) => {
1813
- void handler.onMessage?.(ws, data.toString());
1814
- });
1815
- ws.on("close", (code, reason) => {
1816
- void handler.onClose?.(ws, code, reason);
1817
- });
1818
- ws.on("error", (err) => {
1819
- void handler.onError?.(ws, err);
1820
- });
1821
- });
1822
- } catch {
1823
- socket.destroy();
1824
- }
1825
- })();
2099
+ // src/vite-plugin/virtual-modules-hook.ts
2100
+ function resolveVirtualModuleId(id, ids) {
2101
+ if (id === ids.VIRTUAL_ENTRY_ID) return ids.RESOLVED_ENTRY_ID;
2102
+ if (id === ids.VIRTUAL_MANIFEST_ID) return ids.RESOLVED_MANIFEST_ID;
2103
+ if (id === ids.VIRTUAL_ENTRY_SERVER_ID) return ids.RESOLVED_ENTRY_SERVER_ID;
2104
+ if (id === ids.VIRTUAL_RUNTIME_CONFIG_ID) return ids.RESOLVED_RUNTIME_CONFIG_ID;
2105
+ if (id === DEVTOOLS_VIRTUAL_ID) return DEVTOOLS_RESOLVED_ID;
2106
+ return void 0;
2107
+ }
2108
+ function loadVirtualModule(id, ctx) {
2109
+ if (id === ctx.ids.RESOLVED_ENTRY_ID) {
2110
+ return generateEntryClient(ctx.ssrEnabled, {
2111
+ theoUi: ctx.theoUi?.enabled ? { fonts: ctx.theoUi.config.fonts, theme: ctx.theoUi.config.theme } : void 0
1826
2112
  });
1827
- }).catch(() => {
1828
- console.warn(
1829
- '[Theo] WebSocket routes found but "ws" package not installed. Run: npm install ws'
1830
- );
1831
- });
2113
+ }
2114
+ if (id === ctx.ids.RESOLVED_MANIFEST_ID) {
2115
+ const tree = scanRoutes(ctx.appDir);
2116
+ broadcastRouteManifest(tree);
2117
+ return generateRouteManifest(tree);
2118
+ }
2119
+ if (id === ctx.ids.RESOLVED_ENTRY_SERVER_ID) {
2120
+ return generateEntryServer({
2121
+ streaming: ctx.streamingEnabled,
2122
+ theoUi: ctx.theoUi?.enabled ? { theme: ctx.theoUi.config.theme } : void 0
2123
+ });
2124
+ }
2125
+ if (id === ctx.ids.RESOLVED_RUNTIME_CONFIG_ID) {
2126
+ const tName = ctx.transformer?.name ?? "json";
2127
+ return [
2128
+ `// Generated by Theo Vite plugin`,
2129
+ `;(globalThis).__THEO_TRANSFORMER__ = ${JSON.stringify(tName)}`,
2130
+ `export const TRANSFORMER_NAME = ${JSON.stringify(tName)}`
2131
+ ].join("\n");
2132
+ }
2133
+ if (id === DEVTOOLS_RESOLVED_ID) {
2134
+ return [
2135
+ `// Theo devtools \u2014 virtual entry (DEV only)`,
2136
+ `import('theokit/devtools/entry').catch((e) => console.error('[theo devtools] mount failed', e))`
2137
+ ].join("\n");
2138
+ }
2139
+ return void 0;
1832
2140
  }
1833
2141
 
1834
2142
  // src/vite-plugin/integrations.ts
@@ -1941,8 +2249,8 @@ function findConsumerConfig(projectRoot, basename2) {
1941
2249
  let dir = projectRoot;
1942
2250
  for (let level = 0; level < 3; level++) {
1943
2251
  for (const ext of extensions) {
1944
- const candidate = resolve6(dir, `${basename2}${ext}`);
1945
- if (existsSync7(candidate)) return candidate;
2252
+ const candidate = resolve9(dir, `${basename2}${ext}`);
2253
+ if (existsSync9(candidate)) return candidate;
1946
2254
  }
1947
2255
  const parent = dirname4(dir);
1948
2256
  if (parent === dir) break;
@@ -1961,7 +2269,7 @@ async function theoPluginAsync(rootOrOptions) {
1961
2269
  });
1962
2270
  const servicesPlugins = [];
1963
2271
  if (options.services && Object.keys(options.services).length > 0) {
1964
- const { servicesTypedClientPlugin } = await import("./services-typed-client-XWYYBWGW.js");
2272
+ const { servicesTypedClientPlugin } = await import("./services-typed-client-KK6RHRDG.js");
1965
2273
  servicesPlugins.push(
1966
2274
  servicesTypedClientPlugin({
1967
2275
  cwd: projectRoot,
@@ -1969,20 +2277,20 @@ async function theoPluginAsync(rootOrOptions) {
1969
2277
  })
1970
2278
  );
1971
2279
  }
1972
- const { appTypedClientPlugin } = await import("./app-typed-client-62FYBLVJ.js");
2280
+ const { appTypedClientPlugin } = await import("./app-typed-client-YOMWSA3F.js");
1973
2281
  const appClientPlugin = appTypedClientPlugin({
1974
2282
  cwd: projectRoot,
1975
- serverDir: resolve6(projectRoot, "server"),
1976
- distDir: resolve6(projectRoot, ".theo")
2283
+ serverDir: resolve9(projectRoot, "server"),
2284
+ distDir: resolve9(projectRoot, ".theo")
1977
2285
  });
1978
- const { actionsVirtualModule } = await import("./actions-virtual-module-SEIPACG5.js");
2286
+ const { actionsVirtualModule } = await import("./actions-virtual-module-XNHDNCZ6.js");
1979
2287
  const actionsPlugin = actionsVirtualModule({
1980
- serverDir: resolve6(projectRoot, "server"),
1981
- distDir: resolve6(projectRoot, ".theo")
2288
+ serverDir: resolve9(projectRoot, "server"),
2289
+ distDir: resolve9(projectRoot, ".theo")
1982
2290
  });
1983
2291
  const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-PBHSKCQJ.js");
1984
2292
  const routesHmrPlugin = serverRoutesHmrPlugin({
1985
- serverDir: resolve6(projectRoot, "server")
2293
+ serverDir: resolve9(projectRoot, "server")
1986
2294
  });
1987
2295
  return [
1988
2296
  theoPlugin(rootOrOptions),
@@ -1995,10 +2303,10 @@ async function theoPluginAsync(rootOrOptions) {
1995
2303
  }
1996
2304
  function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
1997
2305
  const include = [];
1998
- if (existsSync7(resolve6(projectRoot, "node_modules", "@theokit", "ui"))) {
2306
+ if (existsSync9(resolve9(projectRoot, "node_modules", "@theokit", "ui"))) {
1999
2307
  include.push("@theokit/ui");
2000
2308
  }
2001
- if (existsSync7(resolve6(projectRoot, "node_modules", "lucide-react"))) {
2309
+ if (existsSync9(resolve9(projectRoot, "node_modules", "lucide-react"))) {
2002
2310
  include.push("lucide-react");
2003
2311
  }
2004
2312
  include.push("devalue");
@@ -2011,10 +2319,20 @@ function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
2011
2319
  }
2012
2320
  return include;
2013
2321
  }
2322
+ var VIRTUAL_MODULE_IDS = {
2323
+ VIRTUAL_ENTRY_ID,
2324
+ RESOLVED_ENTRY_ID,
2325
+ VIRTUAL_MANIFEST_ID,
2326
+ RESOLVED_MANIFEST_ID,
2327
+ VIRTUAL_ENTRY_SERVER_ID,
2328
+ RESOLVED_ENTRY_SERVER_ID,
2329
+ VIRTUAL_RUNTIME_CONFIG_ID,
2330
+ RESOLVED_RUNTIME_CONFIG_ID
2331
+ };
2014
2332
  function theoPlugin(rootOrOptions) {
2015
2333
  const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions ?? {};
2016
2334
  const projectRoot = options.root ?? process.cwd();
2017
- const appDir = resolve6(projectRoot, "app");
2335
+ const appDir = resolve9(projectRoot, "app");
2018
2336
  const ssrEnabled = options.ssr ?? false;
2019
2337
  const currentDir = dirname4(fileURLToPath(import.meta.url));
2020
2338
  const theoSrcDir = resolveTheoRootDir(currentDir);
@@ -2030,7 +2348,7 @@ function theoPlugin(rootOrOptions) {
2030
2348
  let devtoolsEnabled = true;
2031
2349
  let resolvedOpenApi;
2032
2350
  let resolvedDistDir = ".theo";
2033
- let isDevMode = false;
2351
+ const isDevModeRef = { value: false };
2034
2352
  let configLoadedOnce = false;
2035
2353
  return {
2036
2354
  name: "theo",
@@ -2051,223 +2369,73 @@ function theoPlugin(rootOrOptions) {
2051
2369
  resolvedOpenApi = resolved.openapi;
2052
2370
  resolvedDistDir = resolved.distDir;
2053
2371
  },
2054
- // Vite calls this hook (sync return is fine no awaits left after
2055
- // the auto-chain moved to theoPluginAsync). The @theokit/ui +
2056
- // @tailwindcss/vite auto-chain lives in `theoPluginAsync` because
2057
- // Vite drops plugins returned from a child plugin's config() hook.
2372
+ // T2.6 (M6) config() body extracted to config-hook.ts. The hook
2373
+ // owns optimizeDeps + warmup + services proxy + alias cascade.
2374
+ // The @theokit/ui + @tailwindcss/vite auto-chain lives in
2375
+ // `theoPluginAsync` because Vite drops plugins returned from a
2376
+ // child plugin's config() hook.
2058
2377
  config() {
2059
- const ext = existsSync7(resolve6(theoSrcDir, "index.ts")) ? ".ts" : ".js";
2060
- const optimizeDepsInclude = buildOptimizeDepsInclude(projectRoot, options.viteOptimizeDeps);
2061
- const warmupClientFiles = [];
2062
- for (const candidate of ["app/layout.tsx", "app/page.tsx", "app/page.jsx"]) {
2063
- const full = resolve6(projectRoot, candidate);
2064
- if (existsSync7(full)) warmupClientFiles.push(`./${candidate}`);
2065
- }
2066
- const servicesProxy = options.services && Object.keys(options.services).length > 0 ? buildServicesProxyConfig(options.services) : void 0;
2067
- return {
2068
- envPrefix: "THEO_PUBLIC_",
2069
- optimizeDeps: {
2070
- ...optimizeDepsInclude.length > 0 ? { include: optimizeDepsInclude } : {},
2071
- force: true
2072
- },
2073
- server: {
2074
- ...warmupClientFiles.length > 0 ? { warmup: { clientFiles: warmupClientFiles } } : {},
2075
- ...servicesProxy !== void 0 ? { proxy: servicesProxy } : {},
2076
- // Skip watching gitignored heavy dirs to avoid hitting ENOSPC
2077
- // (inotify watcher exhaustion). `referencias/` are deep-research
2078
- // clones; `.theokit/` is per-conversation cache; `.theo/` is
2079
- // build output. None should trigger HMR.
2080
- watch: {
2081
- ignored: [
2082
- "**/referencias/**",
2083
- "**/.theokit/**",
2084
- "**/.theo/**",
2085
- "**/dist/**",
2086
- "**/node_modules/**"
2087
- ]
2088
- }
2089
- },
2090
- resolve: {
2091
- alias: [
2092
- // Order matters: most-specific first so `theokit/X` doesn't
2093
- // get matched by the bare `theokit` alias.
2094
- { find: "theokit/server", replacement: resolve6(theoSrcDir, `server/index${ext}`) },
2095
- { find: "theokit/client", replacement: resolve6(theoSrcDir, `client/index${ext}`) },
2096
- {
2097
- find: "theokit/react-query",
2098
- replacement: resolve6(theoSrcDir, `react-query/index${ext}`)
2099
- },
2100
- {
2101
- find: "theokit/vite-plugin",
2102
- replacement: resolve6(theoSrcDir, `vite-plugin/index${ext}`)
2103
- },
2104
- {
2105
- find: "theokit/adapters/web-shim",
2106
- replacement: resolve6(theoSrcDir, `adapters/web-shim${ext}`)
2107
- },
2108
- {
2109
- find: "theokit/adapters/ws-shim",
2110
- replacement: resolve6(theoSrcDir, `adapters/ws-shim${ext}`)
2111
- },
2112
- // T1.2 — devtools entry (DEV only; tree-shaken in build because
2113
- // the script tag is never injected by inject-devtools.ts in build mode)
2114
- {
2115
- find: "theokit/devtools/entry",
2116
- replacement: resolve6(theoSrcDir, `devtools/entry${ext === ".ts" ? ".tsx" : ".js"}`)
2117
- },
2118
- { find: "theokit", replacement: resolve6(theoSrcDir, `index${ext}`) }
2119
- ]
2120
- }
2121
- };
2378
+ return runConfigHook({
2379
+ projectRoot,
2380
+ theoSrcDir,
2381
+ services: options.services,
2382
+ optimizeDepsInclude: buildOptimizeDepsInclude(projectRoot, options.viteOptimizeDeps)
2383
+ });
2122
2384
  },
2123
- // Auto-inject the entry-client `<script>` into every served HTML
2124
- // (Phase 2 of nextjs-maturity plan). Runs BEFORE Vite's own
2125
- // transform so the injection lands inside <body>, not after
2126
- // Vite-injected content. Idempotent: if the user already wrote
2127
- // the script tag, the HTML is returned unchanged.
2385
+ // T2.6 (M6) transformIndexHtml hook extracted to transform-html-hook.ts.
2386
+ // Preserves the canonical 3-step order (entry-client devtools → styles)
2387
+ // so EC-10 (Vite hook ordering side effects) is honored.
2128
2388
  transformIndexHtml: {
2129
2389
  order: "pre",
2130
2390
  handler(html) {
2131
- const entry = injectEntryClient(html);
2132
- if (entry.warning) console.warn(entry.warning);
2133
- let next = entry.html;
2134
- const devtools = injectDevtoolsScript(next, {
2135
- isDev: isDevMode,
2136
- enabled: devtoolsEnabled
2391
+ return runTransformIndexHtml(html, {
2392
+ isDevMode: isDevModeRef,
2393
+ devtoolsEnabled,
2394
+ projectRoot
2137
2395
  });
2138
- if (devtools.warning) console.warn(devtools.warning);
2139
- next = devtools.html;
2140
- const styles = injectStylesheets(next, {
2141
- isDev: isDevMode,
2142
- hasPackage: (name) => existsSync7(resolve6(projectRoot, "node_modules", ...name.split("/")))
2143
- });
2144
- next = styles.html;
2145
- return next;
2146
2396
  }
2147
2397
  },
2398
+ // T2.6 (M6) — virtual-module resolveId + load dispatch extracted to
2399
+ // virtual-modules-hook.ts. Pure structural split; identity branches
2400
+ // preserved.
2148
2401
  resolveId(id) {
2149
- if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;
2150
- if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;
2151
- if (id === VIRTUAL_ENTRY_SERVER_ID) return RESOLVED_ENTRY_SERVER_ID;
2152
- if (id === VIRTUAL_RUNTIME_CONFIG_ID) return RESOLVED_RUNTIME_CONFIG_ID;
2153
- if (id === DEVTOOLS_VIRTUAL_ID) return DEVTOOLS_RESOLVED_ID;
2402
+ return resolveVirtualModuleId(id, VIRTUAL_MODULE_IDS);
2154
2403
  },
2155
2404
  load(id) {
2156
- if (id === RESOLVED_ENTRY_ID) {
2157
- return generateEntryClient(ssrEnabled, {
2158
- theoUi: theoUi?.enabled ? { fonts: theoUi.config.fonts, theme: theoUi.config.theme } : void 0
2159
- });
2160
- }
2161
- if (id === RESOLVED_MANIFEST_ID) {
2162
- const tree = scanRoutes(appDir);
2163
- broadcastRouteManifest(tree);
2164
- return generateRouteManifest(tree);
2165
- }
2166
- if (id === RESOLVED_ENTRY_SERVER_ID) {
2167
- return generateEntryServer({
2168
- streaming: options.ssrStreaming === true,
2169
- theoUi: theoUi?.enabled ? { theme: theoUi.config.theme } : void 0
2170
- });
2171
- }
2172
- if (id === RESOLVED_RUNTIME_CONFIG_ID) {
2173
- const tName = transformer?.name ?? "json";
2174
- return [
2175
- `// Generated by Theo Vite plugin`,
2176
- `;(globalThis).__THEO_TRANSFORMER__ = ${JSON.stringify(tName)}`,
2177
- `export const TRANSFORMER_NAME = ${JSON.stringify(tName)}`
2178
- ].join("\n");
2179
- }
2180
- if (id === DEVTOOLS_RESOLVED_ID) {
2181
- return [
2182
- `// Theo devtools \u2014 virtual entry (DEV only)`,
2183
- `import('theokit/devtools/entry').catch((e) => console.error('[theo devtools] mount failed', e))`
2184
- ].join("\n");
2185
- }
2405
+ return loadVirtualModule(id, {
2406
+ ids: VIRTUAL_MODULE_IDS,
2407
+ appDir,
2408
+ ssrEnabled,
2409
+ streamingEnabled: options.ssrStreaming === true,
2410
+ theoUi,
2411
+ transformer
2412
+ });
2186
2413
  },
2414
+ // T2.6 (M6) — configureServer body extracted to configure-server-hook.ts.
2415
+ // The hook owns middleware order, ws subscriptions, watcher handlers,
2416
+ // OpenAPI re-emit, and WS upgrade. EC-10 honored (every effect preserved
2417
+ // in its original ordering).
2187
2418
  async configureServer(server) {
2188
- isDevMode = true;
2189
- globalThis.__theoViteHotServer = server;
2190
- server.ws.on("theo:devtools:request-manifest", () => {
2191
- try {
2192
- const tree = scanRoutes(appDir);
2193
- broadcastRouteManifest(tree);
2194
- } catch {
2195
- }
2196
- });
2197
- const serverDir = resolve6(projectRoot, "server");
2198
- server.middlewares.use(
2199
- createActionMiddleware(server, serverDir, { pluginRunner, csrfMode, disallowed })
2200
- );
2201
- const servicesProxyPrefixes = options.services && Object.keys(options.services).length > 0 ? Object.values(options.services).map((s) => s.proxy) : [];
2202
- const csrfReadinessStore = options.csrfReadinessStore ?? new CsrfReadinessStore();
2203
- server.middlewares.use(
2204
- createApiMiddleware(server, serverDir, {
2205
- rateLimitConfig: options.rateLimit,
2206
- pluginRunner,
2207
- batching: resolvedBatching,
2208
- transformer,
2209
- csrfMode,
2210
- securityHeaders,
2211
- disallowed,
2212
- cors,
2213
- auditLogger,
2214
- servicesProxyPrefixes,
2215
- csrfReadinessStore
2216
- })
2217
- );
2218
- const configPath = resolve6(projectRoot, "theo.config.ts");
2219
- server.watcher.on("change", (file) => {
2220
- if (file === configPath) {
2221
- console.warn(
2222
- "\n[theokit] theo.config.ts changed; restart dev server for plugin changes to take effect\n"
2223
- );
2224
- }
2225
- });
2226
- if (ssrEnabled) {
2227
- setupSsrDevMiddleware(server, {
2228
- projectRoot,
2229
- virtualEntryServerId: VIRTUAL_ENTRY_SERVER_ID,
2230
- securityHeaders
2231
- });
2232
- }
2233
- function handleRouteChange(filePath) {
2234
- if (!isRouteFile(basename(filePath))) return;
2235
- if (!filePath.startsWith(appDir)) return;
2236
- const mod = server.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);
2237
- if (mod) {
2238
- server.moduleGraph.invalidateModule(mod);
2239
- server.ws.send({ type: "full-reload" });
2240
- }
2241
- }
2242
- server.watcher.on("add", handleRouteChange);
2243
- server.watcher.on("unlink", handleRouteChange);
2244
- if (resolvedOpenApi !== void 0) {
2245
- const openApiCfg = resolvedOpenApi;
2246
- const { reEmitOpenApi } = await import("./dev-emit-RBSFZZNO.js");
2247
- const openApiServerDir = resolve6(projectRoot, "server");
2248
- const openApiDistDir = resolve6(projectRoot, resolvedDistDir);
2249
- const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
2250
- void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2251
- server.watcher.on("change", (file) => {
2252
- if (isRouteFileForOpenApi(file)) {
2253
- void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2254
- }
2255
- });
2256
- server.watcher.on("add", (file) => {
2257
- if (isRouteFileForOpenApi(file)) {
2258
- void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2259
- }
2260
- });
2261
- server.watcher.on("unlink", (file) => {
2262
- if (isRouteFileForOpenApi(file)) {
2263
- void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2264
- }
2265
- });
2266
- }
2267
- setupWsUpgrade(server, projectRoot);
2268
- server.httpServer?.once("close", () => {
2269
- ;
2270
- globalThis.__theoViteHotServer = void 0;
2419
+ await runConfigureServer(server, {
2420
+ projectRoot,
2421
+ appDir,
2422
+ resolvedDistDir,
2423
+ ssrEnabled,
2424
+ isDevMode: isDevModeRef,
2425
+ pluginRunner,
2426
+ transformer,
2427
+ resolvedBatching,
2428
+ csrfMode,
2429
+ securityHeaders,
2430
+ disallowed,
2431
+ cors,
2432
+ auditLogger,
2433
+ resolvedOpenApi,
2434
+ services: options.services,
2435
+ rateLimit: options.rateLimit,
2436
+ csrfReadinessStoreOverride: options.csrfReadinessStore,
2437
+ virtualEntryServerId: VIRTUAL_ENTRY_SERVER_ID,
2438
+ resolvedManifestId: RESOLVED_MANIFEST_ID
2271
2439
  });
2272
2440
  }
2273
2441
  };
@@ -2278,10 +2446,10 @@ export {
2278
2446
  theoConfigSchema,
2279
2447
  deepMerge,
2280
2448
  loadConfig,
2281
- generateEntryClient,
2282
- generateRouteManifest,
2283
2449
  isRouteFile,
2284
2450
  scanRoutes,
2451
+ generateEntryClient,
2452
+ generateRouteManifest,
2285
2453
  defineTheoIntegration,
2286
2454
  IntegrationVirtualModulePrefixError,
2287
2455
  IntegrationRouteCollisionError,
@@ -2289,4 +2457,4 @@ export {
2289
2457
  theoPluginAsync,
2290
2458
  theoPlugin
2291
2459
  };
2292
- //# sourceMappingURL=chunk-BIGBFZSU.js.map
2460
+ //# sourceMappingURL=chunk-2F4FROEQ.js.map