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
@@ -0,0 +1,63 @@
1
+ // src/server/realtime/channel-manager.ts
2
+ var ChannelManager = class {
3
+ rooms = /* @__PURE__ */ new Map();
4
+ wsRooms = /* @__PURE__ */ new Map();
5
+ subscribe(ws, room) {
6
+ let roomSet = this.rooms.get(room);
7
+ if (!roomSet) {
8
+ roomSet = /* @__PURE__ */ new Set();
9
+ this.rooms.set(room, roomSet);
10
+ }
11
+ roomSet.add(ws);
12
+ let wsSet = this.wsRooms.get(ws);
13
+ if (!wsSet) {
14
+ wsSet = /* @__PURE__ */ new Set();
15
+ this.wsRooms.set(ws, wsSet);
16
+ }
17
+ wsSet.add(room);
18
+ }
19
+ unsubscribe(ws, room) {
20
+ this.rooms.get(room)?.delete(ws);
21
+ if (this.rooms.get(room)?.size === 0) this.rooms.delete(room);
22
+ this.wsRooms.get(ws)?.delete(room);
23
+ if (this.wsRooms.get(ws)?.size === 0) this.wsRooms.delete(ws);
24
+ }
25
+ broadcast(room, data, exclude) {
26
+ const clients = this.rooms.get(room);
27
+ if (!clients) return;
28
+ const msg = JSON.stringify(data);
29
+ for (const ws of clients) {
30
+ if (ws !== exclude) ws.send(msg);
31
+ }
32
+ }
33
+ broadcastAll(data) {
34
+ const msg = JSON.stringify(data);
35
+ const seen = /* @__PURE__ */ new Set();
36
+ for (const clients of this.rooms.values()) {
37
+ for (const ws of clients) {
38
+ if (!seen.has(ws)) {
39
+ ws.send(msg);
40
+ seen.add(ws);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ getRoomSize(room) {
46
+ return this.rooms.get(room)?.size ?? 0;
47
+ }
48
+ cleanup(ws) {
49
+ const rooms = this.wsRooms.get(ws);
50
+ if (rooms) {
51
+ for (const room of rooms) {
52
+ this.rooms.get(room)?.delete(ws);
53
+ if (this.rooms.get(room)?.size === 0) this.rooms.delete(room);
54
+ }
55
+ }
56
+ this.wsRooms.delete(ws);
57
+ }
58
+ };
59
+
60
+ export {
61
+ ChannelManager
62
+ };
63
+ //# sourceMappingURL=chunk-NM4WF3XD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/realtime/channel-manager.ts"],"sourcesContent":["import type { WebSocketLike } from '../define/define-websocket.js'\n\nexport class ChannelManager {\n private rooms = new Map<string, Set<WebSocketLike>>()\n private wsRooms = new Map<WebSocketLike, Set<string>>()\n\n subscribe(ws: WebSocketLike, room: string): void {\n let roomSet = this.rooms.get(room)\n if (!roomSet) {\n roomSet = new Set()\n this.rooms.set(room, roomSet)\n }\n roomSet.add(ws)\n\n let wsSet = this.wsRooms.get(ws)\n if (!wsSet) {\n wsSet = new Set()\n this.wsRooms.set(ws, wsSet)\n }\n wsSet.add(room)\n }\n\n unsubscribe(ws: WebSocketLike, room: string): void {\n this.rooms.get(room)?.delete(ws)\n if (this.rooms.get(room)?.size === 0) this.rooms.delete(room)\n\n this.wsRooms.get(ws)?.delete(room)\n if (this.wsRooms.get(ws)?.size === 0) this.wsRooms.delete(ws)\n }\n\n broadcast(room: string, data: unknown, exclude?: WebSocketLike): void {\n const clients = this.rooms.get(room)\n if (!clients) return\n\n const msg = JSON.stringify(data)\n for (const ws of clients) {\n if (ws !== exclude) ws.send(msg)\n }\n }\n\n broadcastAll(data: unknown): void {\n const msg = JSON.stringify(data)\n const seen = new Set<WebSocketLike>()\n for (const clients of this.rooms.values()) {\n for (const ws of clients) {\n if (!seen.has(ws)) {\n ws.send(msg)\n seen.add(ws)\n }\n }\n }\n }\n\n getRoomSize(room: string): number {\n return this.rooms.get(room)?.size ?? 0\n }\n\n cleanup(ws: WebSocketLike): void {\n const rooms = this.wsRooms.get(ws)\n if (rooms) {\n for (const room of rooms) {\n this.rooms.get(room)?.delete(ws)\n if (this.rooms.get(room)?.size === 0) this.rooms.delete(room)\n }\n }\n this.wsRooms.delete(ws)\n }\n}\n"],"mappings":";AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB,QAAQ,oBAAI,IAAgC;AAAA,EAC5C,UAAU,oBAAI,IAAgC;AAAA,EAEtD,UAAU,IAAmB,MAAoB;AAC/C,QAAI,UAAU,KAAK,MAAM,IAAI,IAAI;AACjC,QAAI,CAAC,SAAS;AACZ,gBAAU,oBAAI,IAAI;AAClB,WAAK,MAAM,IAAI,MAAM,OAAO;AAAA,IAC9B;AACA,YAAQ,IAAI,EAAE;AAEd,QAAI,QAAQ,KAAK,QAAQ,IAAI,EAAE;AAC/B,QAAI,CAAC,OAAO;AACV,cAAQ,oBAAI,IAAI;AAChB,WAAK,QAAQ,IAAI,IAAI,KAAK;AAAA,IAC5B;AACA,UAAM,IAAI,IAAI;AAAA,EAChB;AAAA,EAEA,YAAY,IAAmB,MAAoB;AACjD,SAAK,MAAM,IAAI,IAAI,GAAG,OAAO,EAAE;AAC/B,QAAI,KAAK,MAAM,IAAI,IAAI,GAAG,SAAS,EAAG,MAAK,MAAM,OAAO,IAAI;AAE5D,SAAK,QAAQ,IAAI,EAAE,GAAG,OAAO,IAAI;AACjC,QAAI,KAAK,QAAQ,IAAI,EAAE,GAAG,SAAS,EAAG,MAAK,QAAQ,OAAO,EAAE;AAAA,EAC9D;AAAA,EAEA,UAAU,MAAc,MAAe,SAA+B;AACpE,UAAM,UAAU,KAAK,MAAM,IAAI,IAAI;AACnC,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,eAAW,MAAM,SAAS;AACxB,UAAI,OAAO,QAAS,IAAG,KAAK,GAAG;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,aAAa,MAAqB;AAChC,UAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,UAAM,OAAO,oBAAI,IAAmB;AACpC,eAAW,WAAW,KAAK,MAAM,OAAO,GAAG;AACzC,iBAAW,MAAM,SAAS;AACxB,YAAI,CAAC,KAAK,IAAI,EAAE,GAAG;AACjB,aAAG,KAAK,GAAG;AACX,eAAK,IAAI,EAAE;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY,MAAsB;AAChC,WAAO,KAAK,MAAM,IAAI,IAAI,GAAG,QAAQ;AAAA,EACvC;AAAA,EAEA,QAAQ,IAAyB;AAC/B,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,QAAI,OAAO;AACT,iBAAW,QAAQ,OAAO;AACxB,aAAK,MAAM,IAAI,IAAI,GAAG,OAAO,EAAE;AAC/B,YAAI,KAAK,MAAM,IAAI,IAAI,GAAG,SAAS,EAAG,MAAK,MAAM,OAAO,IAAI;AAAA,MAC9D;AAAA,IACF;AACA,SAAK,QAAQ,OAAO,EAAE;AAAA,EACxB;AACF;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-NPMCKOX4.js.map
@@ -1,59 +1,11 @@
1
- // src/server/http/cookies.ts
2
- function parseCookieHeader(header) {
3
- const map = /* @__PURE__ */ new Map();
4
- if (!header) return map;
5
- for (const pair of header.split(";")) {
6
- const trimmed = pair.trim();
7
- const eqIdx = trimmed.indexOf("=");
8
- if (eqIdx === -1) continue;
9
- const key = trimmed.slice(0, eqIdx);
10
- const rawValue = trimmed.slice(eqIdx + 1);
11
- let value;
12
- try {
13
- value = decodeURIComponent(rawValue);
14
- } catch {
15
- value = rawValue;
16
- }
17
- map.set(key, value);
18
- }
19
- return map;
20
- }
21
- function getCookie(req, name) {
22
- const cookies = parseCookieHeader(req.headers.cookie);
23
- const value = cookies.get(name);
24
- if (value === void 0) return void 0;
25
- if (/(?:%[^0-9A-Fa-f])|(?:%[0-9A-Fa-f]$)/.test(value)) return void 0;
26
- return value;
27
- }
28
- function setCookie(res, name, value, options) {
29
- const opts = {
30
- httpOnly: true,
31
- secure: process.env.NODE_ENV === "production",
32
- sameSite: "lax",
33
- path: "/",
34
- ...options
35
- };
36
- const parts = [`${name}=${encodeURIComponent(value)}`];
37
- if (opts.httpOnly) parts.push("HttpOnly");
38
- if (opts.secure) parts.push("Secure");
39
- const sameSiteCanonical = opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1);
40
- parts.push(`SameSite=${sameSiteCanonical}`);
41
- if (opts.maxAge !== void 0) parts.push(`Max-Age=${opts.maxAge}`);
42
- if (opts.path) parts.push(`Path=${opts.path}`);
43
- if (opts.domain) parts.push(`Domain=${opts.domain}`);
44
- const existing = res.getHeader("Set-Cookie");
45
- let cookies = [];
46
- if (Array.isArray(existing)) {
47
- cookies = existing.map(String);
48
- } else if (existing !== void 0) {
49
- cookies = [String(existing)];
50
- }
51
- cookies.push(parts.join("; "));
52
- res.setHeader("Set-Cookie", cookies);
53
- }
54
- function deleteCookie(res, name, options) {
55
- setCookie(res, name, "", { maxAge: 0, path: options?.path ?? "/" });
56
- }
1
+ import {
2
+ appendCookieToHeaders,
3
+ appendDeleteCookieToHeaders,
4
+ deleteCookie,
5
+ getCookie,
6
+ getCookieFromRequest,
7
+ setCookie
8
+ } from "./chunk-ZJQ4O76G.js";
57
9
 
58
10
  // src/server/auth/crypto.ts
59
11
  var encoder = new TextEncoder();
@@ -143,17 +95,29 @@ function normalizeSecrets(input) {
143
95
  }
144
96
  return arr;
145
97
  }
98
+ async function encryptSessionEnvelope(data, secrets, maxAge) {
99
+ const envelope = {
100
+ data,
101
+ exp: Date.now() + maxAge * 1e3
102
+ };
103
+ return encrypt(envelope, secrets[0]);
104
+ }
105
+ async function decryptSessionWithFallback(raw, secrets) {
106
+ const results = await Promise.all(
107
+ secrets.map(async (secret, i) => {
108
+ const env = await decrypt(raw, secret);
109
+ return env ? { envelope: env, index: i } : null;
110
+ })
111
+ );
112
+ for (const result of results) {
113
+ if (result) return result;
114
+ }
115
+ return null;
116
+ }
146
117
  function createSessionManager(config) {
147
118
  const secrets = normalizeSecrets(config.secret);
148
119
  const cookieName = config.cookieName ?? "theo_session";
149
120
  const maxAge = config.maxAge ?? 604800;
150
- async function encryptEnvelope(data) {
151
- const envelope = {
152
- data,
153
- exp: Date.now() + maxAge * 1e3
154
- };
155
- return encrypt(envelope, secrets[0]);
156
- }
157
121
  function writeCookie(res, token) {
158
122
  setCookie(res, cookieName, token, {
159
123
  httpOnly: true,
@@ -162,18 +126,6 @@ function createSessionManager(config) {
162
126
  path: "/"
163
127
  });
164
128
  }
165
- async function decryptWithFallback(raw) {
166
- const results = await Promise.all(
167
- secrets.map(async (secret, i) => {
168
- const env = await decrypt(raw, secret);
169
- return env ? { envelope: env, index: i } : null;
170
- })
171
- );
172
- for (const result of results) {
173
- if (result) return result;
174
- }
175
- return null;
176
- }
177
129
  return {
178
130
  async getSession(req) {
179
131
  const { data } = await this.getSessionWithMeta(req);
@@ -182,7 +134,7 @@ function createSessionManager(config) {
182
134
  async getSessionWithMeta(req) {
183
135
  const raw = getCookie(req, cookieName);
184
136
  if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
185
- const decoded = await decryptWithFallback(raw);
137
+ const decoded = await decryptSessionWithFallback(raw, secrets);
186
138
  if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
187
139
  if (decoded.envelope.exp < Date.now()) {
188
140
  return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } };
@@ -193,7 +145,7 @@ function createSessionManager(config) {
193
145
  };
194
146
  },
195
147
  async createSession(res, data) {
196
- const token = await encryptEnvelope(data);
148
+ const token = await encryptSessionEnvelope(data, secrets, maxAge);
197
149
  writeCookie(res, token);
198
150
  },
199
151
  destroySession(res) {
@@ -202,12 +154,65 @@ function createSessionManager(config) {
202
154
  async rotateSession(req, res) {
203
155
  const { data } = await this.getSessionWithMeta(req);
204
156
  if (data === null) return null;
205
- const token = await encryptEnvelope(data);
157
+ const token = await encryptSessionEnvelope(data, secrets, maxAge);
206
158
  writeCookie(res, token);
207
159
  return data;
208
160
  }
209
161
  };
210
162
  }
163
+ function createSessionManagerWeb(config) {
164
+ const secrets = normalizeSecrets(config.secret);
165
+ const cookieName = config.cookieName ?? "theo_session";
166
+ const maxAge = config.maxAge ?? 604800;
167
+ function writeCookieWeb(target, token) {
168
+ appendCookieToHeaders(target, cookieName, token, {
169
+ httpOnly: true,
170
+ sameSite: "lax",
171
+ maxAge,
172
+ path: "/"
173
+ });
174
+ }
175
+ return {
176
+ async getSession(request) {
177
+ const { data } = await this.getSessionWithMeta(request);
178
+ return data;
179
+ },
180
+ async getSessionWithMeta(request) {
181
+ const raw = getCookieFromRequest(request, cookieName);
182
+ if (raw === void 0) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
183
+ const decoded = await decryptSessionWithFallback(raw, secrets);
184
+ if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
185
+ if (decoded.envelope.exp < Date.now()) {
186
+ return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } };
187
+ }
188
+ return {
189
+ data: decoded.envelope.data,
190
+ meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 }
191
+ };
192
+ },
193
+ async createSession(target, data) {
194
+ const token = await encryptSessionEnvelope(data, secrets, maxAge);
195
+ writeCookieWeb(target, token);
196
+ },
197
+ destroySession(target) {
198
+ appendDeleteCookieToHeaders(target, cookieName);
199
+ },
200
+ async rotateSession(request, target) {
201
+ const { data } = await this.getSessionWithMeta(request);
202
+ if (data === null) return null;
203
+ const token = await encryptSessionEnvelope(data, secrets, maxAge);
204
+ writeCookieWeb(target, token);
205
+ return data;
206
+ }
207
+ };
208
+ }
209
+ async function rotateIfNeededWeb(sm, request, target) {
210
+ const { data, meta } = await sm.getSessionWithMeta(request);
211
+ if (data !== null && meta.needsReencrypt) {
212
+ await sm.createSession(target, data);
213
+ }
214
+ return data;
215
+ }
211
216
  async function rotateIfNeeded(sm, req, res) {
212
217
  const { data, meta } = await sm.getSessionWithMeta(req);
213
218
  if (data !== null && meta.needsReencrypt) {
@@ -573,14 +578,12 @@ async function discoverOidcProvider(issuer) {
573
578
  }
574
579
 
575
580
  export {
576
- parseCookieHeader,
577
- getCookie,
578
- setCookie,
579
- deleteCookie,
580
581
  encrypt,
581
582
  decrypt,
582
583
  _resetKeyCacheForTests,
583
584
  createSessionManager,
585
+ createSessionManagerWeb,
586
+ rotateIfNeededWeb,
584
587
  rotateIfNeeded,
585
588
  assertProductionSecret,
586
589
  generateTotp,
@@ -598,4 +601,4 @@ export {
598
601
  clearOidcCache,
599
602
  discoverOidcProvider
600
603
  };
601
- //# sourceMappingURL=chunk-VRHXOKRP.js.map
604
+ //# sourceMappingURL=chunk-NZXH2LQQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/auth/crypto.ts","../src/server/auth/session.ts","../src/server/_internal/encoding.ts","../src/server/auth/auth-totp.ts","../src/server/auth/auth-backup-codes.ts","../src/server/auth/auth-throttle.ts","../src/server/auth/oauth-pkce.ts","../src/server/auth/oauth-state.ts","../src/server/auth/oidc-discovery.ts"],"sourcesContent":["// AES-GCM-256 token encryption with cached key derivation.\n//\n// CR-002 fixes:\n// - KDF: HKDF (RFC 5869) over the raw secret, not raw SHA-256.\n// HKDF binds the application context (\"theo-session-v1\") into the\n// derived key so the same secret used elsewhere cannot decrypt these\n// tokens. SHA-256 of the raw secret has neither domain separation nor\n// proper key-stretching guarantees.\n// - Cache: derived `CryptoKey` is memoized per (secret, info) tuple.\n// Pre-fix, every encrypt/decrypt re-ran SHA-256 + importKey, which (a)\n// burned CPU on every authenticated request and (b) made\n// `decryptWithFallback` leak rotation-array position via timing.\n// - Buffer typing: `Uint8Array` flows directly into Web Crypto\n// (`BufferSource`-compatible). The previous `as unknown as ArrayBuffer`\n// double-cast (CR-016) is removed.\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nconst HKDF_INFO_DEFAULT = encoder.encode('theo-session-v1')\nconst HKDF_SALT_EMPTY = new Uint8Array(0)\n\n// Memoize derived keys. Bounded by the number of distinct secrets the app\n// ever uses (typically 1 in steady state, 2-5 during rotation). Map-based\n// so distinct secrets do not evict each other.\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const cached = keyCache.get(secret)\n if (cached) return cached\n\n const derive = (async () => {\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HKDF' },\n false,\n ['deriveKey'],\n )\n return crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: HKDF_SALT_EMPTY, info: HKDF_INFO_DEFAULT },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n })()\n\n keyCache.set(secret, derive)\n // If derivation fails (invalid secret), evict so a retry can try fresh.\n derive.catch(() => {\n keyCache.delete(secret)\n })\n return derive\n}\n\nfunction toBase64Url(data: Uint8Array): string {\n return Buffer.from(data).toString('base64url')\n}\n\n/**\n * Decode a base64url string into a `Uint8Array<ArrayBuffer>`. The explicit\n * generic matters: Web Crypto APIs expect `BufferSource`, and TS 5.7+\n * tightened `Uint8Array` to `Uint8Array<ArrayBufferLike>` which includes\n * `SharedArrayBuffer`. Returning the narrower generic lets the value pass\n * `crypto.subtle.decrypt` without `as` casts (CR-016 follow-through).\n */\nfunction fromBase64Url(str: string): Uint8Array<ArrayBuffer> {\n const buf = Buffer.from(str, 'base64url')\n const copy = new Uint8Array(buf.length)\n copy.set(buf)\n return copy\n}\n\n// The `T` generic exists so call sites get type-safe round-tripping\n// (`encrypt<Session>(s, ...)` pairs with `decrypt<Session>(...)`); the\n// runtime body just stringifies `data`, hence T does not appear in the\n// signature beyond the argument.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the round-trip contract with decrypt<T>\nexport async function encrypt<T>(data: T, secret: string): Promise<string> {\n const key = await deriveKey(secret)\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = encoder.encode(JSON.stringify(data))\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext),\n )\n return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`\n}\n\nexport async function decrypt<T>(token: string, secret: string): Promise<T | null> {\n try {\n const parts = token.split(':')\n if (parts.length !== 2) return null\n\n const iv = fromBase64Url(parts[0])\n const ciphertext = fromBase64Url(parts[1])\n const key = await deriveKey(secret)\n\n const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext)\n return JSON.parse(decoder.decode(plaintext)) as T\n } catch {\n return null\n }\n}\n\n/**\n * Reset the derived-key cache. Test helper — never call from production code.\n * Used by Vitest to guarantee isolation between tests that use distinct\n * secrets and want to confirm the derivation path runs.\n */\nexport function _resetKeyCacheForTests(): void {\n keyCache.clear()\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport {\n getCookie,\n setCookie,\n deleteCookie,\n getCookieFromRequest,\n appendCookieToHeaders,\n appendDeleteCookieToHeaders,\n} from '../http/cookies.js'\n\nimport { encrypt, decrypt } from './crypto.js'\n\nexport interface SessionConfig {\n /**\n * Session secret. Either a single string (legacy) or an array of strings\n * where index 0 is the newest. The array form enables dual-key rotation\n * (T3.1 / ADR D5): encrypt always uses `secrets[0]`, decrypt walks the\n * array, transparent re-encrypt on legacy hits (T3.2).\n *\n * EC-1: array length capped at 5 — enforced via throw at construction.\n */\n secret: string | string[]\n cookieName?: string\n maxAge?: number\n}\n\ninterface SessionEnvelope<T> {\n data: T\n exp: number\n}\n\nexport interface SessionMeta {\n /** Index of the secret that decrypted the session. 0 = newest; > 0 = legacy. */\n secretIndex: number\n /** True when the decrypt used a legacy secret and the cookie should be re-encrypted. */\n needsReencrypt: boolean\n}\n\nexport interface SessionManager<TSession> {\n getSession(req: IncomingMessage): Promise<TSession | null>\n /**\n * T3.2 — Variant of `getSession` that surfaces decrypt metadata.\n * Used by `api-middleware.ts` to wire transparent re-encrypt BEFORE\n * the handler runs (so streaming SSR routes don't miss the Set-Cookie\n * window — EC-4).\n */\n getSessionWithMeta(req: IncomingMessage): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(res: ServerResponse, data: TSession): Promise<void>\n destroySession(res: ServerResponse): void\n /**\n * T3.3 — OWASP A07:2021 session fixation mitigation. Re-encrypts the\n * current session with a fresh IV and refreshed expiry. No-op when no\n * session is present.\n */\n rotateSession(req: IncomingMessage, res: ServerResponse): Promise<TSession | null>\n}\n\n/**\n * EC-1 — array length cap. Enforced via throw at construction. Silent\n * truncation would create false sense of rotation; silent acceptance\n * would create unbounded CPU on legacy decrypt walks.\n */\nconst MAX_SECRETS = 5\n\n/**\n * Validate + normalize the secret config into a non-empty array of\n * strings ≥ 32 chars each.\n */\nfunction normalizeSecrets(input: string | string[]): string[] {\n const arr = Array.isArray(input) ? input.slice() : [input]\n if (arr.length === 0) {\n throw new Error('Session secret must be non-empty (received an empty array)')\n }\n if (arr.length > MAX_SECRETS) {\n throw new Error(\n `Session secret array exceeds maximum of ${MAX_SECRETS} entries — drop the oldest before adding a new one (received ${arr.length})`,\n )\n }\n const wasArray = Array.isArray(input)\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n if (typeof s !== 'string' || s.length < 32) {\n const where = wasArray ? ` at index ${i}` : ''\n throw new Error(`Session secret${where} must be at least 32 characters for secure encryption`)\n }\n }\n return arr\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — module-level pure helpers extracted so\n * `createSessionManager` (IncomingMessage path) and\n * `createSessionManagerWeb` (Web path) share the same encryption +\n * dual-key rotation logic without code duplication (sonarjs\n * no-identical-functions). Both wrappers pass `secrets` + `maxAge`\n * captured from their respective factory scope.\n */\nasync function encryptSessionEnvelope(\n data: unknown,\n secrets: string[],\n maxAge: number,\n): Promise<string> {\n const envelope: SessionEnvelope<unknown> = {\n data,\n exp: Date.now() + maxAge * 1000,\n }\n return encrypt(envelope, secrets[0]) // newest\n}\n\n/**\n * CR-002 constant-time: try EVERY rotation entry in parallel, then pick\n * the newest match. Sequential early-exit leaked rotation position via\n * timing (success on entry 0 returned in ~3 ms; success on entry 4 in\n * ~15 ms). Running in parallel binds wall-clock to the slowest single\n * attempt, independent of which entry actually matched. With derived-key\n * cache (CR-002 in crypto.ts), the CPU multiplier per request is ~0\n * after the first request per secret.\n */\nasync function decryptSessionWithFallback<TSession>(\n raw: string,\n secrets: string[],\n): Promise<{ envelope: SessionEnvelope<TSession>; index: number } | null> {\n const results = await Promise.all(\n secrets.map(async (secret, i) => {\n const env = await decrypt<SessionEnvelope<TSession>>(raw, secret)\n return env ? { envelope: env, index: i } : null\n }),\n )\n for (const result of results) {\n if (result) return result\n }\n return null\n}\n\nexport function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookie(res: ServerResponse, token: string): void {\n setCookie(res, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(req) {\n const { data } = await this.getSessionWithMeta(req)\n return data\n },\n\n async getSessionWithMeta(req) {\n const raw = getCookie(req, cookieName)\n if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(res, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n },\n\n destroySession(res) {\n deleteCookie(res, cookieName)\n },\n\n async rotateSession(req, res) {\n const { data } = await this.getSessionWithMeta(req)\n if (data === null) return null\n // Fresh IV per encrypt call (Web Crypto guarantee — see crypto.ts)\n // + refreshed expiry. Last write wins for the cookie value.\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards session manager.\n *\n * Mirror of `SessionManager<TSession>` for the Web `Request` + `Headers`\n * shape. Same `SessionConfig`, same encryption (`encrypt`/`decrypt` from\n * `./crypto.js`), same dual-key rotation logic (CR-002 constant-time\n * decrypt fallback walk + transparent re-encrypt), same OWASP A07\n * `rotateSession` invariant.\n *\n * **Signature differences vs `SessionManager`:**\n * - Read methods take `Request` (instead of `IncomingMessage`).\n * - Write methods take a `Headers` instance to mutate (instead of\n * `ServerResponse`) — caller passes the headers they're building\n * for their `Response` constructor.\n *\n * Uses `getCookieFromRequest` / `appendCookieToHeaders` /\n * `appendDeleteCookieToHeaders` from Phase B slice 6/6.\n */\nexport interface SessionManagerWeb<TSession> {\n getSession(request: Request): Promise<TSession | null>\n getSessionWithMeta(request: Request): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(target: Headers, data: TSession): Promise<void>\n destroySession(target: Headers): void\n rotateSession(request: Request, target: Headers): Promise<TSession | null>\n}\n\nexport function createSessionManagerWeb<TSession>(\n config: SessionConfig,\n): SessionManagerWeb<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookieWeb(target: Headers, token: string): void {\n appendCookieToHeaders(target, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(request) {\n const { data } = await this.getSessionWithMeta(request)\n return data\n },\n\n async getSessionWithMeta(request) {\n const raw = getCookieFromRequest(request, cookieName)\n if (raw === undefined) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(target, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n },\n\n destroySession(target) {\n appendDeleteCookieToHeaders(target, cookieName)\n },\n\n async rotateSession(request, target) {\n const { data } = await this.getSessionWithMeta(request)\n if (data === null) return null\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards transparent re-encrypt helper.\n *\n * Mirror of `rotateIfNeeded(sm, req, res)` for the Web Request + Headers\n * shape. Reads session via `getSessionWithMeta`; if the cookie was\n * decrypted with a legacy secret (index > 0), re-issues the cookie with\n * the newest secret via `createSession(target, data)`.\n *\n * **EC-4 honest framing:** unlike the IncomingMessage path where the\n * timing constraint is \"before `res.writeHead` fires\", the Web Request\n * path's constraint is \"before the `Response` object is constructed and\n * returned to the runtime\". Caller MUST invoke `rotateIfNeededWeb`\n * BEFORE building the final `Response(body, { headers: target })` so the\n * re-encrypted Set-Cookie lands on the wire.\n */\nexport async function rotateIfNeededWeb<TSession>(\n sm: SessionManagerWeb<TSession>,\n request: Request,\n target: Headers,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(request)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(target, data)\n }\n return data\n}\n\n/**\n * T3.2 — Transparent re-encrypt helper (EC-4 timing-safe wrapper).\n *\n * Call this in your `createContext` (before any rendering / streaming\n * starts). It:\n * 1. Reads the session via `getSessionWithMeta`.\n * 2. If the cookie was decrypted with a legacy secret (index > 0), it\n * immediately re-issues the cookie with `secrets[0]` so the next\n * request lands on the newest key.\n * 3. Returns the session data (or null) for use downstream.\n *\n * EC-4: this MUST run before `renderToPipeableStream` / `res.writeHead`\n * fires. Calling it inside an SSR component or handler body that has\n * already streamed bytes makes the re-encrypt a silent no-op (Set-Cookie\n * is locked once headers commit), trapping users on the legacy secret\n * forever once it's removed from the array.\n */\nexport async function rotateIfNeeded<TSession>(\n sm: SessionManager<TSession>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(req)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(res, data)\n }\n return data\n}\n\n/**\n * EC-2 — Production secret guard. Refuses to boot when `NODE_ENV === 'production'`\n * AND the secret is too short (< 32 chars) OR matches a known placeholder.\n *\n * Accepts a single secret OR an array. In array form, each entry is validated\n * independently — a single bad entry refuses boot with an index-qualified message.\n */\nconst PLACEHOLDER_PATTERN = /CHANGE_ME|demo[-_]|placeholder/i\n\nexport function assertProductionSecret(secret: string | string[]): void {\n const arr = Array.isArray(secret) ? secret : [secret]\n const isProd = process.env.NODE_ENV === 'production'\n\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n const isPlaceholder = PLACEHOLDER_PATTERN.test(s)\n const isTooShort = s.length < 32\n const prefix = arr.length > 1 ? `Session secret at index ${i}` : 'Session secret'\n\n if (isProd) {\n if (isTooShort) {\n throw new Error(\n `${prefix} too short for production (${s.length} chars; minimum 32). ` +\n `Set a 32+ random char secret in your env (e.g., \\`openssl rand -hex 32\\`).`,\n )\n }\n if (isPlaceholder) {\n throw new Error(\n `${prefix} looks like a placeholder (\"${s.slice(0, 16)}…\") and NODE_ENV is \"production\". ` +\n `Replace it with a 32+ random char secret (e.g., \\`openssl rand -hex 32\\`) before deploying.`,\n )\n }\n continue\n }\n if (isPlaceholder || isTooShort) {\n console.warn(\n `[theokit] WARNING: ${prefix.toLowerCase()} is a placeholder or too short. ` +\n `This is OK for dev, but the production server will REFUSE to boot until you replace it.`,\n )\n }\n }\n}\n","/**\n * Shared encoding utilities for crypto-adjacent code.\n *\n * CR-020 DRY: `base64urlEncode` was duplicated across `oauth-pkce.ts` and\n * `oauth-state.ts`. Duplication in cryptographic helpers is a bug class —\n * a future RFC-driven tweak (e.g., padding semantics) would diverge across\n * files. Centralizing the single canonical implementation eliminates that\n * vector.\n *\n * Module is intentionally under `_internal/` so it is **not** part of the\n * public API surface. Consumers outside `packages/theo/src/server` must\n * not import from here.\n */\n\n/**\n * RFC 4648 §5 — base64url-encode bytes without padding. Used wherever an\n * RFC requires URL-safe base64 (PKCE per RFC 7636 §4.1, JWT per RFC 7515,\n * etc.). Pure function — does not mutate `bytes`.\n */\nexport function base64urlEncode(bytes: Uint8Array): string {\n let s = ''\n for (const b of bytes) s += String.fromCharCode(b)\n // The `=+$` trailing-padding regex is bounded (at most 2 chars for\n // base64). sonarjs flags the `+` quantifier conservatively, but the\n // input is the output of `btoa()` — a string with at most 2 trailing\n // `=` characters by spec.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded trailing-padding pattern (max 2 chars)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Constant-time string equality.\n *\n * CR-012: prior implementations early-exited on length mismatch\n * (`if (a.length !== b.length) return false`). That early exit leaks\n * the comparison-target length via wall-clock timing — observable when\n * the secret length is itself sensitive (length-based bucket assignment,\n * variable-length nonces). This implementation always iterates\n * `max(a.length, b.length)` characters, OR-folding mismatches into a\n * single accumulator that is checked once at the end.\n *\n * Returns `false` for empty inputs as a defensive default — never accept\n * \"both empty\" as a match.\n */\nexport function constantTimeEquals(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false\n if (a.length === 0 || b.length === 0) return false\n\n const maxLen = Math.max(a.length, b.length)\n let diff = a.length ^ b.length\n for (let i = 0; i < maxLen; i++) {\n // charCodeAt returns NaN for out-of-range indices; (NaN ^ x) is x, so we\n // explicitly substitute 0 to keep the OR-fold meaningful.\n const ca = i < a.length ? a.charCodeAt(i) : 0\n const cb = i < b.length ? b.charCodeAt(i) : 0\n diff |= ca ^ cb\n }\n return diff === 0\n}\n","/**\n * T6.2 — RFC 6238 TOTP primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc6238\n * HMAC-based one-time password (HOTP) base: https://datatracker.ietf.org/doc/html/rfc4226\n *\n * Pure-function, dependency-free implementation via Web Crypto. Secrets\n * may be passed as Uint8Array (preferred) or base32 string.\n *\n * SECURITY NOTES (documented per EC-13):\n * - TOTP secrets are equivalent to passwords. Encrypt at rest using a\n * separate KMS key from the session secret. If your DB leaks, ALL\n * 2FA codes are compromised — rotate by forcing all users to re-enroll.\n * - Use `verifyTotp` constant-time (we use crypto.timingSafeEqual internally).\n */\n\n// CR-012 fix: use the shared constant-time helper that does not early-exit\n// on length mismatch (the previous local version did, leaking the digit\n// count via timing).\nimport { constantTimeEquals } from '../_internal/encoding.js'\n\n/** Supported HMAC algorithms per RFC 6238 §1.2. Default SHA-1. */\nexport type TotpAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512'\n\nexport interface TotpOptions {\n /** Shared secret — bytes (preferred) OR base32 string. */\n secret: Uint8Array | string\n /** Time step in seconds. Default 30 (RFC 6238 §5.2 recommended). */\n step?: number\n /** Code length: 6, 7, or 8 digits. Default 6. */\n digits?: 6 | 7 | 8\n /** HMAC algorithm. Default SHA-1 (RFC default). */\n algorithm?: TotpAlgorithm\n /** Time in ms since epoch. Default Date.now(). */\n time?: number\n}\n\nexport interface VerifyTotpOptions extends TotpOptions {\n /**\n * Drift tolerance in number of steps on each side. Default 1\n * (RFC 6238 §5.2 recommends ±1 step = 90s total window).\n */\n window?: number\n}\n\nconst DEFAULT_STEP = 30\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_ALGORITHM: TotpAlgorithm = 'SHA-1'\n\n/** Decode base32 (RFC 4648, no padding) to bytes. Throws on invalid input. */\nfunction base32Decode(s: string): Uint8Array {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n // Trailing `=` is bounded by spec to 0..6 chars; `\\s+` collapses runs\n // of whitespace. Input length is the user's TOTP secret (10..50 chars\n // typical), so super-linear backtracking cannot escalate.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded inputs (TOTP secrets are short)\n const clean = s.toUpperCase().replace(/=+$/, '').replace(/\\s+/g, '')\n if (!/^[A-Z2-7]+$/.test(clean)) {\n throw new Error('Invalid base32 secret')\n }\n const bits: number[] = []\n for (const c of clean) {\n const idx = alphabet.indexOf(c)\n for (let i = 4; i >= 0; i--) bits.push((idx >> i) & 1)\n }\n // Trim trailing partial byte\n const fullBytes = Math.floor(bits.length / 8)\n const out = new Uint8Array(fullBytes)\n for (let i = 0; i < fullBytes; i++) {\n let v = 0\n for (let j = 0; j < 8; j++) v = (v << 1) | bits[i * 8 + j]\n out[i] = v\n }\n return out\n}\n\n/** Encode bytes to base32 (RFC 4648, no padding). */\nfunction base32Encode(bytes: Uint8Array): string {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n let out = ''\n let buffer = 0\n let bits = 0\n for (const b of bytes) {\n buffer = (buffer << 8) | b\n bits += 8\n while (bits >= 5) {\n out += alphabet[(buffer >> (bits - 5)) & 0x1f]\n bits -= 5\n }\n }\n if (bits > 0) out += alphabet[(buffer << (5 - bits)) & 0x1f]\n return out\n}\n\nfunction normalizeSecret(secret: Uint8Array | string): Uint8Array {\n if (typeof secret === 'string') return base32Decode(secret)\n return secret\n}\n\n/** Counter (8-byte big-endian) from time in ms / step in seconds. */\nfunction counterBytes(timeMs: number, stepSec: number): Uint8Array {\n const counter = Math.floor(timeMs / 1000 / stepSec)\n const out = new Uint8Array(8)\n // Two 32-bit halves (Number can safely represent up to 2^53; OK until year 285,000+)\n const hi = Math.floor(counter / 0x100000000)\n const lo = counter >>> 0\n out[0] = (hi >>> 24) & 0xff\n out[1] = (hi >>> 16) & 0xff\n out[2] = (hi >>> 8) & 0xff\n out[3] = hi & 0xff\n out[4] = (lo >>> 24) & 0xff\n out[5] = (lo >>> 16) & 0xff\n out[6] = (lo >>> 8) & 0xff\n out[7] = lo & 0xff\n return out\n}\n\nasync function hmac(\n secret: Uint8Array,\n message: Uint8Array,\n algorithm: TotpAlgorithm,\n): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n secret as BufferSource,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign'],\n )\n const sig = await crypto.subtle.sign('HMAC', key, message as BufferSource)\n return new Uint8Array(sig)\n}\n\n/** RFC 4226 §5.3 dynamic truncation. */\nfunction truncate(mac: Uint8Array, digits: number): string {\n const offset = mac[mac.length - 1] & 0x0f\n const code =\n ((mac[offset] & 0x7f) << 24) |\n ((mac[offset + 1] & 0xff) << 16) |\n ((mac[offset + 2] & 0xff) << 8) |\n (mac[offset + 3] & 0xff)\n const mod = 10 ** digits\n return String(code % mod).padStart(digits, '0')\n}\n\n/** Generate a TOTP code for a given time. */\nexport async function generateTotp(opts: TotpOptions): Promise<string> {\n const secret = normalizeSecret(opts.secret)\n const step = opts.step ?? DEFAULT_STEP\n const digits = opts.digits ?? DEFAULT_DIGITS\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const mac = await hmac(secret, counterBytes(time, step), algorithm)\n return truncate(mac, digits)\n}\n\n/**\n * Verify a TOTP code against the current window ± `window` steps.\n *\n * Returns `false` for malformed tokens (non-digit, wrong length) without\n * throwing — defensive against accidental user-input passthrough.\n */\nexport async function verifyTotp(token: string, opts: VerifyTotpOptions): Promise<boolean> {\n if (typeof token !== 'string') return false\n const digits = opts.digits ?? DEFAULT_DIGITS\n if (!/^\\d+$/.test(token) || token.length !== digits) return false\n\n const step = opts.step ?? DEFAULT_STEP\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const drift = opts.window ?? 1\n const secret = normalizeSecret(opts.secret)\n\n // Walk the whole drift range constant-time-ish (no early exit).\n let matched = false\n for (let delta = -drift; delta <= drift; delta++) {\n const t = time + delta * step * 1000\n const code = truncate(await hmac(secret, counterBytes(t, step), algorithm), digits)\n if (constantTimeEquals(code, token)) matched = true\n }\n return matched\n}\n\n/** Generate a cryptographically random TOTP secret. Default 20 bytes (RFC minimum). */\nexport function generateTotpSecret(bytes = 20): Uint8Array {\n if (bytes < 16) {\n throw new Error('TOTP secret must be at least 16 bytes (RFC 6238 §3 minimum)')\n }\n const out = new Uint8Array(bytes)\n crypto.getRandomValues(out)\n return out\n}\n\n/**\n * Build an `otpauth://` URI for QR code enrollment. Format follows the\n * de facto Google Authenticator spec adopted by every major TOTP app.\n * otpauth://totp/<issuer>:<account>?secret=<base32>&issuer=<issuer>&algorithm=<alg>&digits=<n>&period=<sec>\n */\nexport interface TotpUriOptions {\n secret: Uint8Array\n issuer: string\n account: string\n algorithm?: TotpAlgorithm\n digits?: 6 | 7 | 8\n step?: number\n}\n\nexport function totpUri(opts: TotpUriOptions): string {\n const label = `${opts.issuer}:${opts.account}`\n const params = new URLSearchParams({\n secret: base32Encode(opts.secret),\n issuer: opts.issuer,\n algorithm: opts.algorithm ?? DEFAULT_ALGORITHM,\n digits: String(opts.digits ?? DEFAULT_DIGITS),\n period: String(opts.step ?? DEFAULT_STEP),\n })\n return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`\n}\n","/**\n * T6.3 — Backup codes for 2FA recovery.\n *\n * generateBackupCodes returns N plaintexts (display once to user) + hashes\n * (store in DB). verifyBackupCode constant-time-walks all hashes and\n * returns the matchedHash so caller can delete the used code from storage\n * — replay protection lives at the caller (we can't see their database).\n *\n * SECURITY NOTES:\n * - Codes have ~40 bits entropy (8 chars × 5 bits per char from a\n * 32-char alphabet excluding I/L/O/0/1). Single-use → argon2id\n * overhead is unnecessary; SHA-256 of the normalized code suffices.\n * - The caller MUST atomically delete the matched hash on successful\n * verify. Without that, the code is replayable. We surface\n * matchedHash explicitly so this step is conspicuous.\n * - Pair this with throttleLoginAttempts (T6.1) on the verify endpoint\n * to prevent online brute-force against the 40-bit space.\n */\n\nexport interface BackupCodeOptions {\n /** How many codes to generate. Default 10. */\n count?: number\n /** Length in chars (excluding separator). Default 8. */\n length?: number\n /** Separator inserted at the midpoint. Default '-'; pass null to omit. */\n separator?: '-' | null\n /** Custom alphabet. Default excludes ambiguous chars (I, L, O, 0, 1). */\n alphabet?: string\n}\n\nexport interface BackupCode {\n plaintext: string\n hash: string\n}\n\nconst DEFAULT_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // no I, O, 0, 1\nconst DEFAULT_COUNT = 10\nconst DEFAULT_LENGTH = 8\n\n/**\n * Generate cryptographically random backup codes + their SHA-256 hashes.\n *\n * Plaintext is uppercase alphanumeric (default alphabet excludes ambiguous\n * chars). Separator inserted at the midpoint (default `-`).\n */\nexport async function generateBackupCodes(opts: BackupCodeOptions = {}): Promise<BackupCode[]> {\n const count = opts.count ?? DEFAULT_COUNT\n const length = opts.length ?? DEFAULT_LENGTH\n const separator = opts.separator === undefined ? '-' : opts.separator\n const alphabet = opts.alphabet ?? DEFAULT_ALPHABET\n\n // CR-022 fix: bound count and length explicitly. Pathological inputs\n // (count=Infinity, length=0) would cause an infinite loop or wasteful\n // resource usage. Bounds are generous (count ≤ 1000, length ∈ [4, 64])\n // — well above any realistic 2FA backup-codes use case.\n if (!Number.isInteger(count) || count <= 0 || count > 1000) {\n throw new RangeError(\n `generateBackupCodes: count must be an integer in [1, 1000] (got ${String(count)})`,\n )\n }\n if (!Number.isInteger(length) || length < 4 || length > 64) {\n throw new RangeError(\n `generateBackupCodes: length must be an integer in [4, 64] (got ${String(length)})`,\n )\n }\n if (alphabet.length < 8) {\n throw new RangeError(\n `generateBackupCodes: alphabet must contain at least 8 distinct chars (got ${alphabet.length})`,\n )\n }\n\n const codes: BackupCode[] = []\n const seen = new Set<string>()\n while (codes.length < count) {\n const raw = randomFromAlphabet(length, alphabet)\n if (seen.has(raw)) continue\n seen.add(raw)\n const plaintext = formatWithSeparator(raw, separator)\n const hash = await sha256Hex(normalizeCode(plaintext))\n codes.push({ plaintext, hash })\n }\n return codes\n}\n\n/**\n * Verify a code against a list of stored hashes. Walks all hashes\n * constant-time (no early exit). Returns `valid:true` + matchedHash on\n * hit; `valid:false` otherwise.\n *\n * `matchedHash` is the EXACT string the caller stored, ready to be passed\n * to a `DELETE FROM backup_codes WHERE hash = ?` query.\n */\nexport async function verifyBackupCode(\n code: string,\n hashes: readonly string[],\n): Promise<{ valid: boolean; matchedHash?: string }> {\n const candidate = await sha256Hex(normalizeCode(code))\n let matchedHash: string | undefined\n // Constant-time iteration over hashes: never short-circuit.\n for (const h of hashes) {\n if (constantTimeEquals(h, candidate)) {\n matchedHash = h\n }\n }\n return matchedHash ? { valid: true, matchedHash } : { valid: false }\n}\n\n/**\n * Normalize a code for hashing: uppercase + strip separator. This way\n * `abcd-efgh` and `ABCDEFGH` produce the same hash. Whitespace is also\n * stripped defensively.\n */\nfunction normalizeCode(input: string): string {\n return input.toUpperCase().replace(/[-\\s]+/g, '')\n}\n\nfunction formatWithSeparator(raw: string, separator: '-' | null): string {\n if (!separator || raw.length < 4) return raw\n const mid = Math.floor(raw.length / 2)\n return raw.slice(0, mid) + separator + raw.slice(mid)\n}\n\nfunction randomFromAlphabet(length: number, alphabet: string): string {\n const random = new Uint8Array(length)\n crypto.getRandomValues(random)\n let out = ''\n for (let i = 0; i < length; i++) {\n out += alphabet[random[i] % alphabet.length]\n }\n return out\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { RateLimitStore } from '../rate-limit/rate-limit-store.js'\n\n/**\n * T6.1 — Login throttling primitive.\n *\n * Per-credential failure counter backed by any `RateLimitStore`. After\n * `maxAttempts` failures within `windowMs`, the identifier is locked\n * for `lockoutMs`. Successful login resets the counter.\n *\n * Usage in a login handler:\n *\n * const state = await checkThrottle({ store, identifier: hash(email) })\n * if (!state.allowed) return 429\n * const valid = await verifyPassword(creds)\n * await recordAttempt({ store, identifier: hash(email) }, valid)\n * if (!valid) return 401\n *\n * SECURITY:\n * - NEVER pass raw email/username as `identifier`. Hash or normalize\n * first (e.g. SHA-256 of the lowercased email). This prevents PII\n * from landing in the rate-limit store or audit logs.\n * - Combine with the per-route IP rate limit (T2.2) for layered defense:\n * IP-level limits stop scripted floods; credential-level limits stop\n * distributed brute force.\n */\n\nexport interface ThrottleOptions {\n store: RateLimitStore\n /**\n * Stable identifier per credential. Hash raw emails/usernames before\n * passing in — never leak PII to the rate-limit key space.\n */\n identifier: string\n /** Failures before lockout. Default 5. */\n maxAttempts?: number\n /** Sliding window for counting failures. Default 15 minutes. */\n windowMs?: number\n /** Lockout duration after maxAttempts hit. Default 1 hour. */\n lockoutMs?: number\n}\n\nexport interface ThrottleState {\n allowed: boolean\n remainingAttempts: number\n /** Set when `allowed === false`. Absolute timestamp when the lockout expires. */\n lockedUntil?: Date\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 5\nconst DEFAULT_LOCKOUT_MS = 60 * 60_000\n\n/**\n * Read the current throttle state for an identifier WITHOUT modifying\n * the counter. Call before validating credentials so locked attempts\n * are rejected fast.\n */\nexport async function checkThrottle(opts: ThrottleOptions): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n const state = await opts.store.get(opts.identifier)\n if (!state) {\n return { allowed: true, remainingAttempts: maxAttempts }\n }\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n // Lockout expires when the store TTL fires.\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n\n/**\n * Record an attempt outcome. On success → reset the counter. On failure\n * → increment within the sliding window. Returns the new throttle state.\n *\n * Storage model: the store entry's TTL is `lockoutMs` (which doubles as\n * the failure-accumulation window). When `lockoutMs` is short — common\n * in tests or for soft lockouts — the entry auto-expires and the next\n * attempt starts fresh.\n */\nexport async function recordAttempt(\n opts: ThrottleOptions,\n success: boolean,\n): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n if (success) {\n await opts.store.reset(opts.identifier)\n return {\n allowed: true,\n remainingAttempts: maxAttempts,\n }\n }\n const lockoutMs = opts.lockoutMs ?? DEFAULT_LOCKOUT_MS\n const state = await opts.store.incr(opts.identifier, lockoutMs)\n\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n","/**\n * T7.3 — RFC 7636 PKCE primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc7636\n *\n * Pure-function, dependency-free implementation using Web Crypto.\n *\n * Usage in an OAuth authorization-code flow:\n *\n * const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkceChallenge()\n * // Store codeVerifier in the user's session\n * // Send codeChallenge + codeChallengeMethod in the /authorize redirect\n * // ... user authenticates with provider ...\n * // On /callback, send codeVerifier with the code → token exchange\n */\n\nexport interface PkceChallenge {\n codeVerifier: string\n codeChallenge: string\n codeChallengeMethod: 'S256'\n}\n\n// CR-020 DRY: single canonical base64url encoder lives in _internal/encoding.\nimport { base64urlEncode } from '../_internal/encoding.js'\n\n/**\n * Compute the code_challenge from a code_verifier per RFC 7636 §4.2.\n * Exported separately to enable RFC 7636 Appendix B vector testing.\n */\nexport async function pkceChallengeFromVerifier(verifier: string): Promise<string> {\n const bytes = new TextEncoder().encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return base64urlEncode(new Uint8Array(hash))\n}\n\n/**\n * Generate a fresh PKCE challenge pair.\n *\n * Verifier: 32 cryptographically random bytes → 43-char base64url string.\n * RFC 7636 §4.1 allows 43–128 chars; we ship the minimum-safe default.\n *\n * Method: 'S256' only — no 'plain' fallback. RFC 7636 §7.2 strongly\n * discourages plain and modern providers reject it.\n */\nexport async function generatePkceChallenge(): Promise<PkceChallenge> {\n const random = new Uint8Array(32)\n crypto.getRandomValues(random)\n const codeVerifier = base64urlEncode(random)\n const codeChallenge = await pkceChallengeFromVerifier(codeVerifier)\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }\n}\n","/**\n * T7.4 — OAuth `state` parameter helpers (RFC 6749 §10.12).\n *\n * `state` is a cryptographically random anti-CSRF token. The client\n * stores it (in session/cookie/db) before redirecting to the\n * authorization endpoint and verifies it on the callback. Without it,\n * an attacker can authorize on the user's account using a captured code.\n */\n\n// CR-020 DRY + CR-012 constant-time: shared helpers in _internal/encoding.\nimport { base64urlEncode, constantTimeEquals } from '../_internal/encoding.js'\n\n/**\n * Generate a cryptographically random state token. Default 32 bytes\n * (~43 base64url chars). Increase for higher entropy if your provider\n * supports it.\n */\nexport function generateOAuthState(opts: { bytes?: number } = {}): string {\n const len = opts.bytes ?? 32\n const buf = new Uint8Array(len)\n crypto.getRandomValues(buf)\n return base64urlEncode(buf)\n}\n\n/**\n * Verify a state token. Constant-time compare. Empty inputs always fail\n * (defensive — never accept empty as \"they match\").\n *\n * CR-012 fix: `constantTimeEquals` does not early-exit on length mismatch,\n * so the comparison time is independent of how the input was forged.\n */\nexport function verifyOAuthState(provided: string, stored: string): boolean {\n return constantTimeEquals(provided, stored)\n}\n","/**\n * T7.4 — OpenID Connect Discovery 1.0 metadata fetcher.\n *\n * Reference: https://openid.net/specs/openid-connect-discovery-1_0.html\n *\n * Caches metadata in module scope. Cache key is the exact issuer string\n * (trailing-slash sensitive per RFC 8414 §3). Failures are NOT cached —\n * subsequent calls retry the fetch.\n *\n * EC-7 — HTTPS enforced. RFC 8414 §3 requires HTTPS for OIDC issuers.\n * Localhost (loopback) is allowed for development.\n */\n\nexport interface OidcMetadata {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n jwks_uri?: string\n userinfo_endpoint?: string\n end_session_endpoint?: string\n scopes_supported?: string[]\n response_types_supported?: string[]\n grant_types_supported?: string[]\n id_token_signing_alg_values_supported?: string[]\n [key: string]: unknown // OIDC providers ship many optional fields\n}\n\nconst cache = new Map<string, Promise<OidcMetadata>>()\n\n/** Clear the discovery cache. Used by tests. */\nexport function clearOidcCache(): void {\n cache.clear()\n}\n\nfunction isLoopback(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1'\n}\n\n/**\n * Fetch (or return cached) OIDC provider metadata.\n *\n * Throws when:\n * - Issuer URL is malformed\n * - Issuer is HTTP and not a loopback host (EC-7)\n * - HTTP response is non-OK\n * - Metadata lacks `authorization_endpoint` (sanity check)\n */\nexport async function discoverOidcProvider(issuer: string | URL): Promise<OidcMetadata> {\n const issuerStr = typeof issuer === 'string' ? issuer : issuer.toString()\n // Validate protocol BEFORE checking cache — invalid issuers should never poison.\n const url = new URL(issuerStr)\n if (url.protocol !== 'https:' && !isLoopback(url.hostname)) {\n throw new Error(\n `OIDC issuer must use HTTPS (received \"${url.protocol}//${url.hostname}…\"). ` +\n `RFC 8414 §3 forbids plain HTTP except for loopback hosts.`,\n )\n }\n\n const cached = cache.get(issuerStr)\n if (cached) return cached\n\n const promise = (async () => {\n const wellKnown = new URL(\n '.well-known/openid-configuration',\n issuerStr.endsWith('/') ? issuerStr : issuerStr + '/',\n )\n const res = await fetch(wellKnown)\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status} ${res.statusText} at ${wellKnown}`)\n }\n const metadata = (await res.json()) as OidcMetadata\n if (!metadata.authorization_endpoint) {\n throw new Error('OIDC metadata missing authorization_endpoint — provider response is invalid')\n }\n return metadata\n })()\n // Cache the promise; on rejection, remove so the next call retries.\n cache.set(issuerStr, promise)\n promise.catch(() => {\n if (cache.get(issuerStr) === promise) cache.delete(issuerStr)\n })\n return promise\n}\n"],"mappings":"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","names":["constantTimeEquals"]}
@@ -1,9 +1,9 @@
1
- import {
2
- walkSourceFiles
3
- } from "./chunk-X2VVCJ4V.js";
4
1
  import {
5
2
  HTTP_METHODS
6
3
  } from "./chunk-WSJKACWB.js";
4
+ import {
5
+ walkSourceFiles
6
+ } from "./chunk-X2VVCJ4V.js";
7
7
 
8
8
  // src/server/scan/match.ts
9
9
  function compilePattern(routePath) {
@@ -256,4 +256,4 @@ export {
256
256
  scanServerRoutes,
257
257
  scanWebSocketRoutes
258
258
  };
259
- //# sourceMappingURL=chunk-B6RP57M3.js.map
259
+ //# sourceMappingURL=chunk-OLPB36O2.js.map
@@ -0,0 +1,142 @@
1
+ // src/config/load-env.ts
2
+ import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
3
+ import { resolve } from "path";
4
+ import dotenv from "dotenv";
5
+ import { expand } from "dotenv-expand";
6
+ var MAX_ENV_FILE_BYTES = 1048576;
7
+ var cache = /* @__PURE__ */ new Map();
8
+ function _resetEnvCache() {
9
+ cache.clear();
10
+ }
11
+ function envFilesInPriorityOrder(mode) {
12
+ const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`];
13
+ if (mode === "test") {
14
+ return files.filter((f) => f !== ".env.local");
15
+ }
16
+ return files;
17
+ }
18
+ function readEnvFile(path) {
19
+ let stat;
20
+ try {
21
+ stat = statSync(path);
22
+ } catch {
23
+ return null;
24
+ }
25
+ if (!stat.isFile() && !stat.isFIFO()) return null;
26
+ if (stat.size > MAX_ENV_FILE_BYTES) {
27
+ console.warn(
28
+ `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes \u2014 skipping (likely a generated artifact, not a real env file)`
29
+ );
30
+ return null;
31
+ }
32
+ try {
33
+ const lstat = lstatSync(path);
34
+ if (lstat.isSymbolicLink()) {
35
+ const real = realpathSync(path);
36
+ console.info(`[theokit] .env at ${path} is a symlink \u2192 ${real}`);
37
+ }
38
+ } catch {
39
+ }
40
+ try {
41
+ const content = readFileSync(path, "utf-8");
42
+ return dotenv.parse(content);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function loadEnv(options = {}) {
48
+ const cwd = options.cwd ?? process.cwd();
49
+ const mode = options.mode ?? process.env.NODE_ENV ?? "development";
50
+ const cacheKey = `${cwd}:${mode}`;
51
+ if (!options.forceReload) {
52
+ const cached = cache.get(cacheKey);
53
+ if (cached) return cached;
54
+ }
55
+ const fileNames = envFilesInPriorityOrder(mode);
56
+ const filePaths = fileNames.map((f) => resolve(cwd, f));
57
+ const merged = {};
58
+ const loadedFromFiles = [];
59
+ for (const path of [...filePaths].reverse()) {
60
+ const parsed = readEnvFile(path);
61
+ if (parsed === null) continue;
62
+ for (const [k, v] of Object.entries(parsed)) {
63
+ merged[k] = v;
64
+ }
65
+ loadedFromFiles.unshift(path);
66
+ }
67
+ if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === void 0) {
68
+ process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV;
69
+ }
70
+ delete merged.NODE_ENV;
71
+ const processEnvClone = {};
72
+ for (const [k, v] of Object.entries(process.env)) {
73
+ if (typeof v === "string") processEnvClone[k] = v;
74
+ }
75
+ try {
76
+ expand({ parsed: merged, processEnv: processEnvClone });
77
+ } catch (err) {
78
+ if (err instanceof RangeError) {
79
+ console.warn(
80
+ `[theokit] .env expansion overflow (likely circular reference like A=\${B}, B=\${A}). Leaving values as literals.`
81
+ );
82
+ } else {
83
+ throw err;
84
+ }
85
+ }
86
+ const loaded = {};
87
+ for (const [k, v] of Object.entries(merged)) {
88
+ if (process.env[k] !== void 0) continue;
89
+ process.env[k] = v;
90
+ loaded[k] = v;
91
+ }
92
+ process.env.__THEOKIT_PROCESSED_ENV = "true";
93
+ const result = { loaded, loadedFromFiles };
94
+ cache.set(cacheKey, result);
95
+ return result;
96
+ }
97
+
98
+ // src/server/transformer.ts
99
+ import superjson from "superjson";
100
+ var superjsonTransformer = {
101
+ name: "superjson",
102
+ serialize: (v) => JSON.stringify(superjson.serialize(v)),
103
+ deserialize: (raw) => {
104
+ const parsed = JSON.parse(raw);
105
+ return superjson.deserialize(parsed);
106
+ }
107
+ };
108
+ var jsonTransformer = {
109
+ name: "json",
110
+ serialize: (v) => JSON.stringify(v),
111
+ deserialize: (raw) => JSON.parse(raw)
112
+ };
113
+ var BUILT_INS = {
114
+ superjson: superjsonTransformer,
115
+ json: jsonTransformer
116
+ };
117
+ function resolveTransformer(selector) {
118
+ if (typeof selector === "string") {
119
+ const built = BUILT_INS[selector];
120
+ if (built === void 0) {
121
+ throw new Error(
122
+ `Unknown transformer "${selector}". Built-in options: ${Object.keys(BUILT_INS).join(", ")}.`
123
+ );
124
+ }
125
+ return built;
126
+ }
127
+ if (typeof selector !== "object" || typeof selector.serialize !== "function" || typeof selector.deserialize !== "function") {
128
+ throw new Error(
129
+ `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`
130
+ );
131
+ }
132
+ return selector;
133
+ }
134
+
135
+ export {
136
+ _resetEnvCache,
137
+ loadEnv,
138
+ superjsonTransformer,
139
+ jsonTransformer,
140
+ resolveTransformer
141
+ };
142
+ //# sourceMappingURL=chunk-PIVX3DYW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/load-env.ts","../src/server/transformer.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Reads `.env` files from a CLI-controlled `cwd`. File names are a fixed\n * literal set. Build-time + CLI tool; no HTTP input.\n */\n/**\n * `loadEnv()` — auto-loads `.env` files into `process.env` for server code.\n *\n * Implements the Next.js `loadEnvConfig` algorithm\n * (`referencias/next.js/packages/next-env/index.ts:114-180`) with TheoKit\n * adaptations:\n *\n * - EC-1: 1MB file-size cap (anti-OOM, anti-supply-chain).\n * - EC-2: `_resetEnvCache()` test-only side-door.\n * - EC-8: `dotenv-expand` circular-ref safe (lib-provided).\n * - EC-13: log when `.env` is a symlink (transparency).\n * - D6: real `process.env` wins over `.env`-file values.\n * - NODE_ENV stash: `.env`-set NODE_ENV NEVER overrides the real\n * `process.env.NODE_ENV`. Stashed in `__THEOKIT_USER_NODE_ENV` instead.\n */\n\nimport { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport dotenv from 'dotenv'\nimport { expand, type DotenvPopulateInput } from 'dotenv-expand'\n\nimport type { LoadEnvOptions, LoadEnvResult } from './load-env-types.js'\n\n/** EC-1: 1MB cap. Real `.env`s are < 10KB. */\nconst MAX_ENV_FILE_BYTES = 1_048_576\n\nconst cache = new Map<string, LoadEnvResult>()\n\n/** Test-only side-door for EC-2 — clear the module-level cache between vitest test files. */\nexport function _resetEnvCache(): void {\n cache.clear()\n}\n\nfunction envFilesInPriorityOrder(mode: string): string[] {\n // Top of list = highest priority. Will be read in REVERSE so first-in\n // wins via overwrite during merge.\n const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`]\n // Test mode skips `.env.local` per dotenv convention (avoid leaking dev\n // secrets into the test suite).\n if (mode === 'test') {\n return files.filter((f) => f !== '.env.local')\n }\n return files\n}\n\nfunction readEnvFile(path: string): Record<string, string> | null {\n let stat\n try {\n stat = statSync(path)\n } catch {\n return null // ENOENT / EACCES → skip\n }\n\n // Allow files + FIFOs (1Password/SOPS pipe support).\n if (!stat.isFile() && !stat.isFIFO()) return null\n\n // EC-1 — anti-OOM cap.\n if (stat.size > MAX_ENV_FILE_BYTES) {\n console.warn(\n `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes — skipping (likely a generated artifact, not a real env file)`,\n )\n return null\n }\n\n // EC-13 — symlink transparency. Don't refuse; just log.\n try {\n const lstat = lstatSync(path)\n if (lstat.isSymbolicLink()) {\n const real = realpathSync(path)\n // eslint-disable-next-line no-console -- intentional transparency log on a build-time tool\n console.info(`[theokit] .env at ${path} is a symlink → ${real}`)\n }\n } catch {\n // lstat failure is non-fatal — fall through to read.\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return dotenv.parse(content)\n } catch {\n return null\n }\n}\n\n// eslint-disable-next-line complexity -- canonical env-load algorithm (priority → expand → guards → apply); inlining branches is clearer than extracting micro-helpers\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult {\n const cwd = options.cwd ?? process.cwd()\n const mode = options.mode ?? process.env.NODE_ENV ?? 'development'\n const cacheKey = `${cwd}:${mode}`\n\n if (!options.forceReload) {\n const cached = cache.get(cacheKey)\n if (cached) return cached\n }\n\n const fileNames = envFilesInPriorityOrder(mode)\n const filePaths = fileNames.map((f) => resolve(cwd, f))\n\n // Read in REVERSE — lower-priority files first, so higher-priority\n // (lower-index) overwrites during merge.\n const merged: Record<string, string> = {}\n const loadedFromFiles: string[] = []\n for (const path of [...filePaths].reverse()) {\n const parsed = readEnvFile(path)\n if (parsed === null) continue\n for (const [k, v] of Object.entries(parsed)) {\n merged[k] = v\n }\n loadedFromFiles.unshift(path) // keep priority order in result\n }\n\n // NODE_ENV stash — `.env`-set NODE_ENV never overrides real process.env.NODE_ENV.\n if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === undefined) {\n process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV\n }\n delete merged.NODE_ENV // never propagate\n\n // EC-8 — dotenv-expand@11 stack-overflows on circular refs (A=${B}, B=${A}).\n // Wrap in try/catch — on overflow, leave the parsed map untouched (literal\n // values survive). Tested via test_loadEnv_circular_expand_no_loop_EC8.\n // Pass a CLONE of process.env (string-only, filtered) so expand can\n // reference real env without mutating it.\n const processEnvClone: DotenvPopulateInput = {}\n for (const [k, v] of Object.entries(process.env)) {\n if (typeof v === 'string') processEnvClone[k] = v\n }\n try {\n expand({ parsed: merged, processEnv: processEnvClone })\n } catch (err) {\n if (err instanceof RangeError) {\n console.warn(\n `[theokit] .env expansion overflow (likely circular reference like A=\\${B}, B=\\${A}). Leaving values as literals.`,\n )\n } else {\n throw err\n }\n }\n\n // Apply: D6 — real process.env wins over file values.\n const loaded: Record<string, string> = {}\n for (const [k, v] of Object.entries(merged)) {\n if (process.env[k] !== undefined) continue // real env wins\n process.env[k] = v\n loaded[k] = v\n }\n\n // Sentinel\n process.env.__THEOKIT_PROCESSED_ENV = 'true'\n\n const result: LoadEnvResult = { loaded, loadedFromFiles }\n cache.set(cacheKey, result)\n return result\n}\n","import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n"],"mappings":"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","names":[]}
@@ -10,6 +10,7 @@ var ActionScanError = class extends Error {
10
10
  conflictingPaths;
11
11
  constructor(code, message, conflictingPaths) {
12
12
  super(message);
13
+ this.name = "ActionScanError";
13
14
  this.code = code;
14
15
  this.conflictingPaths = conflictingPaths;
15
16
  }
@@ -152,4 +153,4 @@ export {
152
153
  scanServerActions,
153
154
  scanServerActionsEnriched
154
155
  };
155
- //# sourceMappingURL=chunk-7TOMTMHT.js.map
156
+ //# sourceMappingURL=chunk-R7OC6UDK.js.map