@kyro-cms/core 0.9.0 → 0.9.2

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 (265) hide show
  1. package/README.md +55 -593
  2. package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-BKszZlG0.d.cts} +1 -1
  3. package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-Ccf1j-IN.d.ts} +1 -1
  4. package/dist/api-handler-graphql.cjs +44 -0
  5. package/dist/api-handler-graphql.cjs.map +1 -0
  6. package/dist/api-handler-graphql.d.cts +6 -0
  7. package/dist/api-handler-graphql.d.ts +6 -0
  8. package/dist/api-handler-graphql.js +41 -0
  9. package/dist/api-handler-graphql.js.map +1 -0
  10. package/dist/api-handler-trpc.cjs +38 -0
  11. package/dist/api-handler-trpc.cjs.map +1 -0
  12. package/dist/api-handler-trpc.d.cts +5 -0
  13. package/dist/api-handler-trpc.d.ts +5 -0
  14. package/dist/api-handler-trpc.js +36 -0
  15. package/dist/api-handler-trpc.js.map +1 -0
  16. package/dist/api-handler.cjs +33 -99
  17. package/dist/api-handler.cjs.map +1 -1
  18. package/dist/api-handler.d.cts +2 -1
  19. package/dist/api-handler.d.ts +2 -1
  20. package/dist/api-handler.js +21 -97
  21. package/dist/api-handler.js.map +1 -1
  22. package/dist/{tenant-B1YB0Jy8.d.ts → base-CIuXkrH4.d.cts} +7 -15
  23. package/dist/{tenant-Cpeveji6.d.cts → base-fFo4lqER.d.ts} +7 -15
  24. package/dist/bootstrap-3PV3GJ3S.js +7 -0
  25. package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-3PV3GJ3S.js.map} +1 -1
  26. package/dist/bootstrap-4CELFLJO.cjs +32 -0
  27. package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-4CELFLJO.cjs.map} +1 -1
  28. package/dist/{chunk-VJT6P4N6.cjs → chunk-3HR772HI.cjs} +199 -32
  29. package/dist/chunk-3HR772HI.cjs.map +1 -0
  30. package/dist/chunk-3KTWGODI.cjs +178 -0
  31. package/dist/chunk-3KTWGODI.cjs.map +1 -0
  32. package/dist/{chunk-QXIQWPAP.js → chunk-3UK5XBVJ.js} +4 -134
  33. package/dist/chunk-3UK5XBVJ.js.map +1 -0
  34. package/dist/{chunk-FXYP2HA6.js → chunk-4AO3A3JM.js} +48 -4
  35. package/dist/chunk-4AO3A3JM.js.map +1 -0
  36. package/dist/{chunk-Z6ZWNWWR.js → chunk-4CV4JOE5.js} +3 -9
  37. package/dist/{chunk-Z6ZWNWWR.js.map → chunk-4CV4JOE5.js.map} +1 -1
  38. package/dist/chunk-4M7X5HAB.cjs +173 -0
  39. package/dist/chunk-4M7X5HAB.cjs.map +1 -0
  40. package/dist/chunk-53NYVYVX.js +3243 -0
  41. package/dist/chunk-53NYVYVX.js.map +1 -0
  42. package/dist/{chunk-35U3FROB.js → chunk-5H3MWQJS.js} +714 -184
  43. package/dist/chunk-5H3MWQJS.js.map +1 -0
  44. package/dist/{chunk-YVUJBEXE.cjs → chunk-5PMQQFRE.cjs} +16 -7
  45. package/dist/chunk-5PMQQFRE.cjs.map +1 -0
  46. package/dist/{chunk-57P6MJKC.js → chunk-6UNONDW7.js} +94 -10
  47. package/dist/chunk-6UNONDW7.js.map +1 -0
  48. package/dist/{chunk-Y3N7UUDO.js → chunk-7OGPN7MP.js} +5 -2
  49. package/dist/chunk-7OGPN7MP.js.map +1 -0
  50. package/dist/{chunk-2OL4O2TH.cjs → chunk-7OS7TX2Q.cjs} +68 -62
  51. package/dist/chunk-7OS7TX2Q.cjs.map +1 -0
  52. package/dist/{chunk-3TPQ2BU6.js → chunk-BYBMTIMT.js} +2 -6
  53. package/dist/chunk-BYBMTIMT.js.map +1 -0
  54. package/dist/{chunk-ES5HNFFT.js → chunk-CF7OL6HR.js} +4 -2
  55. package/dist/chunk-CF7OL6HR.js.map +1 -0
  56. package/dist/chunk-CJONKRHJ.js +162 -0
  57. package/dist/chunk-CJONKRHJ.js.map +1 -0
  58. package/dist/{chunk-OHVB4AJ7.js → chunk-CJX74IYK.js} +24 -18
  59. package/dist/chunk-CJX74IYK.js.map +1 -0
  60. package/dist/{chunk-5KVM3WEY.cjs → chunk-CNKT4PME.cjs} +1592 -868
  61. package/dist/chunk-CNKT4PME.cjs.map +1 -0
  62. package/dist/{chunk-G7VZBCD6.cjs → chunk-CZLDE2OZ.cjs} +2 -9
  63. package/dist/{chunk-G7VZBCD6.cjs.map → chunk-CZLDE2OZ.cjs.map} +1 -1
  64. package/dist/{chunk-WQBRWOQT.cjs → chunk-DPA3KWPY.cjs} +4 -3
  65. package/dist/chunk-DPA3KWPY.cjs.map +1 -0
  66. package/dist/{chunk-LINKCEG4.cjs → chunk-E2763JUP.cjs} +726 -196
  67. package/dist/chunk-E2763JUP.cjs.map +1 -0
  68. package/dist/chunk-E5UJBLQ7.js +220 -0
  69. package/dist/chunk-E5UJBLQ7.js.map +1 -0
  70. package/dist/{chunk-DVD5P72E.cjs → chunk-EEJUFDMF.cjs} +2 -6
  71. package/dist/chunk-EEJUFDMF.cjs.map +1 -0
  72. package/dist/chunk-FSKONGCX.cjs +253 -0
  73. package/dist/chunk-FSKONGCX.cjs.map +1 -0
  74. package/dist/{chunk-Y3QQN7PN.js → chunk-GAAHG2Z4.js} +13 -4
  75. package/dist/chunk-GAAHG2Z4.js.map +1 -0
  76. package/dist/chunk-GAOXD3XT.js +175 -0
  77. package/dist/chunk-GAOXD3XT.js.map +1 -0
  78. package/dist/{chunk-SA7NSSIQ.cjs → chunk-GUUB5EAG.cjs} +13 -187
  79. package/dist/chunk-GUUB5EAG.cjs.map +1 -0
  80. package/dist/{chunk-4DA7QPLA.cjs → chunk-GXFOGU7N.cjs} +5 -2
  81. package/dist/chunk-GXFOGU7N.cjs.map +1 -0
  82. package/dist/{chunk-I7HHI6QV.cjs → chunk-IDVRRRAK.cjs} +17 -9
  83. package/dist/chunk-IDVRRRAK.cjs.map +1 -0
  84. package/dist/{chunk-HXRD4B37.js → chunk-IPTZM3VE.js} +1423 -704
  85. package/dist/chunk-IPTZM3VE.js.map +1 -0
  86. package/dist/chunk-KC2GDBLS.cjs +84 -0
  87. package/dist/chunk-KC2GDBLS.cjs.map +1 -0
  88. package/dist/{chunk-QUW2RZTM.cjs → chunk-L46ROHUS.cjs} +51 -7
  89. package/dist/chunk-L46ROHUS.cjs.map +1 -0
  90. package/dist/chunk-L4EZKIEX.js +185 -0
  91. package/dist/chunk-L4EZKIEX.js.map +1 -0
  92. package/dist/{chunk-REK7AYOC.js → chunk-L5UKKZQN.js} +199 -32
  93. package/dist/chunk-L5UKKZQN.js.map +1 -0
  94. package/dist/chunk-NKPKR5BW.cjs +188 -0
  95. package/dist/chunk-NKPKR5BW.cjs.map +1 -0
  96. package/dist/chunk-NWUEVLQT.cjs +99 -0
  97. package/dist/chunk-NWUEVLQT.cjs.map +1 -0
  98. package/dist/{chunk-3AJE4SEG.js → chunk-OHC6UHFY.js} +208 -76
  99. package/dist/chunk-OHC6UHFY.js.map +1 -0
  100. package/dist/chunk-PHJRNPHY.cjs +3291 -0
  101. package/dist/chunk-PHJRNPHY.cjs.map +1 -0
  102. package/dist/{chunk-DXHRBMGB.js → chunk-PQ72Z6WC.js} +67 -112
  103. package/dist/chunk-PQ72Z6WC.js.map +1 -0
  104. package/dist/{chunk-K7JPTH3G.cjs → chunk-PV2I2KMI.cjs} +214 -82
  105. package/dist/chunk-PV2I2KMI.cjs.map +1 -0
  106. package/dist/{chunk-PDYFVNUX.cjs → chunk-Q23GAMLE.cjs} +71 -116
  107. package/dist/chunk-Q23GAMLE.cjs.map +1 -0
  108. package/dist/{chunk-H727JIG7.js → chunk-Q72BOAPK.js} +16 -8
  109. package/dist/chunk-Q72BOAPK.js.map +1 -0
  110. package/dist/{chunk-IBG6V56E.cjs → chunk-QFLB4EIJ.cjs} +2 -139
  111. package/dist/chunk-QFLB4EIJ.cjs.map +1 -0
  112. package/dist/{chunk-2KVHZE6O.cjs → chunk-RFFSZSCL.cjs} +282 -190
  113. package/dist/chunk-RFFSZSCL.cjs.map +1 -0
  114. package/dist/{chunk-V3LKPM3O.cjs → chunk-SHTTJMLT.cjs} +4 -2
  115. package/dist/chunk-SHTTJMLT.cjs.map +1 -0
  116. package/dist/{chunk-WOWUL7ZY.js → chunk-UUDTPZX6.js} +5 -4
  117. package/dist/chunk-UUDTPZX6.js.map +1 -0
  118. package/dist/{chunk-QPPDLRNR.js → chunk-V7KZQIZ6.js} +277 -185
  119. package/dist/chunk-V7KZQIZ6.js.map +1 -0
  120. package/dist/{chunk-3ZFYL34R.js → chunk-WXVB364T.js} +12 -185
  121. package/dist/chunk-WXVB364T.js.map +1 -0
  122. package/dist/chunk-XEB7PH2E.js +81 -0
  123. package/dist/chunk-XEB7PH2E.js.map +1 -0
  124. package/dist/{chunk-IA6AU5PI.cjs → chunk-Y7AQK4R4.cjs} +94 -10
  125. package/dist/chunk-Y7AQK4R4.cjs.map +1 -0
  126. package/dist/chunk-YFAVQQTU.js +92 -0
  127. package/dist/chunk-YFAVQQTU.js.map +1 -0
  128. package/dist/cli/index.cjs +6 -6
  129. package/dist/cli/index.cjs.map +1 -1
  130. package/dist/cli/index.js +6 -6
  131. package/dist/cli/index.js.map +1 -1
  132. package/dist/client.cjs +4 -4
  133. package/dist/client.d.cts +3 -3
  134. package/dist/client.d.ts +3 -3
  135. package/dist/client.js +2 -2
  136. package/dist/drizzle/index.cjs +15 -14
  137. package/dist/drizzle/index.d.cts +10 -14
  138. package/dist/drizzle/index.d.ts +10 -14
  139. package/dist/drizzle/index.js +6 -5
  140. package/dist/fields/index.cjs +22 -38
  141. package/dist/fields/index.d.cts +2 -22
  142. package/dist/fields/index.d.ts +2 -22
  143. package/dist/fields/index.js +2 -2
  144. package/dist/graphql/index.cjs +6 -5
  145. package/dist/graphql/index.d.cts +5 -3
  146. package/dist/graphql/index.d.ts +5 -3
  147. package/dist/graphql/index.js +4 -3
  148. package/dist/index-BKta3cBH.d.cts +277 -0
  149. package/dist/index-ClOqnkTO.d.ts +277 -0
  150. package/dist/index.cjs +310 -168
  151. package/dist/index.cjs.map +1 -1
  152. package/dist/index.d.cts +130 -211
  153. package/dist/index.d.ts +130 -211
  154. package/dist/index.js +174 -35
  155. package/dist/index.js.map +1 -1
  156. package/dist/integration.cjs +3 -3
  157. package/dist/integration.js +2 -2
  158. package/dist/media-7WDX4BDJ.js +4 -0
  159. package/dist/{media-GPPTZ43E.js.map → media-7WDX4BDJ.js.map} +1 -1
  160. package/dist/{media-XNTUFJZR.cjs → media-TUSLVRQ6.cjs} +3 -3
  161. package/dist/{media-XNTUFJZR.cjs.map → media-TUSLVRQ6.cjs.map} +1 -1
  162. package/dist/mongo-auth-adapter-GT4S7SCU.cjs +17 -0
  163. package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-GT4S7SCU.cjs.map} +1 -1
  164. package/dist/mongo-auth-adapter-M7VV4LNB.js +4 -0
  165. package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-M7VV4LNB.js.map} +1 -1
  166. package/dist/mongodb/index.cjs +9 -8
  167. package/dist/mongodb/index.d.cts +6 -13
  168. package/dist/mongodb/index.d.ts +6 -13
  169. package/dist/mongodb/index.js +5 -4
  170. package/dist/postgres-auth-adapter-AFAPISH7.js +5 -0
  171. package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-AFAPISH7.js.map} +1 -1
  172. package/dist/postgres-auth-adapter-SFDTLONT.cjs +14 -0
  173. package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-SFDTLONT.cjs.map} +1 -1
  174. package/dist/redis-adapter-UQX4EE3B.cjs +13 -0
  175. package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-UQX4EE3B.cjs.map} +1 -1
  176. package/dist/redis-adapter-XALOGWY3.js +4 -0
  177. package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-XALOGWY3.js.map} +1 -1
  178. package/dist/rest/index.cjs +16 -15
  179. package/dist/rest/index.d.cts +4 -4
  180. package/dist/rest/index.d.ts +4 -4
  181. package/dist/rest/index.js +14 -13
  182. package/dist/{schema-37SE2F4B.cjs → schema-6QL3USNB.cjs} +15 -15
  183. package/dist/{schema-37SE2F4B.cjs.map → schema-6QL3USNB.cjs.map} +1 -1
  184. package/dist/{schema-5PHL5IVB.js → schema-FNNWEAAW.js} +4 -4
  185. package/dist/{schema-5PHL5IVB.js.map → schema-FNNWEAAW.js.map} +1 -1
  186. package/dist/sqlite-adapter-AQB5TCGV.cjs +13 -0
  187. package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-AQB5TCGV.cjs.map} +1 -1
  188. package/dist/sqlite-adapter-N5H6IM2X.js +4 -0
  189. package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-N5H6IM2X.js.map} +1 -1
  190. package/dist/templates/index.cjs +134 -32
  191. package/dist/templates/index.d.cts +52 -9
  192. package/dist/templates/index.d.ts +52 -9
  193. package/dist/templates/index.js +4 -2
  194. package/dist/trpc/index.cjs +14 -13
  195. package/dist/trpc/index.d.cts +55 -49
  196. package/dist/trpc/index.d.ts +55 -49
  197. package/dist/trpc/index.js +5 -4
  198. package/dist/{types-D6ZLRGbH.d.cts → types-CpjuXbe7.d.cts} +2 -0
  199. package/dist/{types-D6ZLRGbH.d.ts → types-CpjuXbe7.d.ts} +2 -0
  200. package/dist/{types-VtjUxIMp.d.cts → types-DeSApf9T.d.cts} +36 -14
  201. package/dist/{types-VtjUxIMp.d.ts → types-DeSApf9T.d.ts} +36 -14
  202. package/dist/{types-J3R9nVsZ.d.cts → types-Dgzlftb7.d.ts} +32 -28
  203. package/dist/{types-Bs1up4yP.d.ts → types-Ds0tCA3L.d.cts} +32 -28
  204. package/dist/ws/index.cjs +6 -6
  205. package/dist/ws/index.js +2 -2
  206. package/package.json +22 -4
  207. package/dist/bootstrap-AKAUP6F6.cjs +0 -32
  208. package/dist/bootstrap-JCML6NFO.js +0 -7
  209. package/dist/chunk-2KVHZE6O.cjs.map +0 -1
  210. package/dist/chunk-2OL4O2TH.cjs.map +0 -1
  211. package/dist/chunk-35U3FROB.js.map +0 -1
  212. package/dist/chunk-3AJE4SEG.js.map +0 -1
  213. package/dist/chunk-3J4MFTI3.js +0 -3872
  214. package/dist/chunk-3J4MFTI3.js.map +0 -1
  215. package/dist/chunk-3TPQ2BU6.js.map +0 -1
  216. package/dist/chunk-3ZFYL34R.js.map +0 -1
  217. package/dist/chunk-4DA7QPLA.cjs.map +0 -1
  218. package/dist/chunk-57P6MJKC.js.map +0 -1
  219. package/dist/chunk-5KVM3WEY.cjs.map +0 -1
  220. package/dist/chunk-6IMPH6WV.cjs +0 -3897
  221. package/dist/chunk-6IMPH6WV.cjs.map +0 -1
  222. package/dist/chunk-ATBOUGQP.cjs +0 -513
  223. package/dist/chunk-ATBOUGQP.cjs.map +0 -1
  224. package/dist/chunk-DVD5P72E.cjs.map +0 -1
  225. package/dist/chunk-DXHRBMGB.js.map +0 -1
  226. package/dist/chunk-ES5HNFFT.js.map +0 -1
  227. package/dist/chunk-FXYP2HA6.js.map +0 -1
  228. package/dist/chunk-H727JIG7.js.map +0 -1
  229. package/dist/chunk-HXRD4B37.js.map +0 -1
  230. package/dist/chunk-I7HHI6QV.cjs.map +0 -1
  231. package/dist/chunk-IA6AU5PI.cjs.map +0 -1
  232. package/dist/chunk-IBG6V56E.cjs.map +0 -1
  233. package/dist/chunk-K7JPTH3G.cjs.map +0 -1
  234. package/dist/chunk-LINKCEG4.cjs.map +0 -1
  235. package/dist/chunk-OHVB4AJ7.js.map +0 -1
  236. package/dist/chunk-PDYFVNUX.cjs.map +0 -1
  237. package/dist/chunk-Q23JB3KL.js +0 -488
  238. package/dist/chunk-Q23JB3KL.js.map +0 -1
  239. package/dist/chunk-QPPDLRNR.js.map +0 -1
  240. package/dist/chunk-QUW2RZTM.cjs.map +0 -1
  241. package/dist/chunk-QXIQWPAP.js.map +0 -1
  242. package/dist/chunk-R3XIBBAW.cjs +0 -34
  243. package/dist/chunk-R3XIBBAW.cjs.map +0 -1
  244. package/dist/chunk-REK7AYOC.js.map +0 -1
  245. package/dist/chunk-SA7NSSIQ.cjs.map +0 -1
  246. package/dist/chunk-SDMNUYVU.js +0 -30
  247. package/dist/chunk-SDMNUYVU.js.map +0 -1
  248. package/dist/chunk-V3LKPM3O.cjs.map +0 -1
  249. package/dist/chunk-VJT6P4N6.cjs.map +0 -1
  250. package/dist/chunk-WOWUL7ZY.js.map +0 -1
  251. package/dist/chunk-WQBRWOQT.cjs.map +0 -1
  252. package/dist/chunk-Y3N7UUDO.js.map +0 -1
  253. package/dist/chunk-Y3QQN7PN.js.map +0 -1
  254. package/dist/chunk-YVUJBEXE.cjs.map +0 -1
  255. package/dist/index-CLp-DRKA.d.ts +0 -64
  256. package/dist/index-DfO7G4kN.d.cts +0 -64
  257. package/dist/media-GPPTZ43E.js +0 -4
  258. package/dist/mongo-auth-adapter-NHHUJHVH.cjs +0 -17
  259. package/dist/mongo-auth-adapter-NJQUUCTP.js +0 -4
  260. package/dist/postgres-auth-adapter-3T2NKTSE.js +0 -5
  261. package/dist/postgres-auth-adapter-7IEENCKQ.cjs +0 -14
  262. package/dist/redis-adapter-D2E2S3GB.cjs +0 -13
  263. package/dist/redis-adapter-VQXD7ESY.js +0 -4
  264. package/dist/sqlite-adapter-LVK5PS4T.cjs +0 -13
  265. package/dist/sqlite-adapter-TR3U3W6Q.js +0 -4
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var chunkIA6AU5PI_cjs = require('./chunk-IA6AU5PI.cjs');
4
- var chunkI7HHI6QV_cjs = require('./chunk-I7HHI6QV.cjs');
3
+ var chunkY7AQK4R4_cjs = require('./chunk-Y7AQK4R4.cjs');
4
+ var chunkKC2GDBLS_cjs = require('./chunk-KC2GDBLS.cjs');
5
+ var chunkIDVRRRAK_cjs = require('./chunk-IDVRRRAK.cjs');
5
6
  var chunkADLJSJSN_cjs = require('./chunk-ADLJSJSN.cjs');
6
- var chunkIBG6V56E_cjs = require('./chunk-IBG6V56E.cjs');
7
- var chunkVJT6P4N6_cjs = require('./chunk-VJT6P4N6.cjs');
8
- var chunkR3XIBBAW_cjs = require('./chunk-R3XIBBAW.cjs');
9
- var chunk2KVHZE6O_cjs = require('./chunk-2KVHZE6O.cjs');
10
- var chunk2OL4O2TH_cjs = require('./chunk-2OL4O2TH.cjs');
11
- var chunkPDYFVNUX_cjs = require('./chunk-PDYFVNUX.cjs');
12
- var chunk4DA7QPLA_cjs = require('./chunk-4DA7QPLA.cjs');
13
- var chunkSA7NSSIQ_cjs = require('./chunk-SA7NSSIQ.cjs');
14
- var chunkG7VZBCD6_cjs = require('./chunk-G7VZBCD6.cjs');
7
+ var chunkQFLB4EIJ_cjs = require('./chunk-QFLB4EIJ.cjs');
8
+ var chunk4M7X5HAB_cjs = require('./chunk-4M7X5HAB.cjs');
9
+ var chunkRFFSZSCL_cjs = require('./chunk-RFFSZSCL.cjs');
10
+ var chunk7OS7TX2Q_cjs = require('./chunk-7OS7TX2Q.cjs');
11
+ var chunkQ23GAMLE_cjs = require('./chunk-Q23GAMLE.cjs');
12
+ var chunkGXFOGU7N_cjs = require('./chunk-GXFOGU7N.cjs');
13
+ var chunkNKPKR5BW_cjs = require('./chunk-NKPKR5BW.cjs');
14
+ var chunkCZLDE2OZ_cjs = require('./chunk-CZLDE2OZ.cjs');
15
15
  var crypto = require('crypto');
16
16
  var unstorage = require('unstorage');
17
17
  var fsDriver = require('unstorage/drivers/fs');
@@ -19,11 +19,13 @@ var hono = require('hono');
19
19
  var path = require('path');
20
20
  var fs = require('fs');
21
21
  var sharp = require('sharp');
22
- var graphql = require('graphql');
23
22
  var promises = require('fs/promises');
23
+ var process2 = require('process');
24
24
  var clientS3 = require('@aws-sdk/client-s3');
25
+ var nodeHttpHandler = require('@smithy/node-http-handler');
25
26
  var stream = require('stream');
26
27
  var basicFtp = require('basic-ftp');
28
+ var zodToJsonSchema = require('zod-to-json-schema');
27
29
 
28
30
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
29
31
 
@@ -31,6 +33,7 @@ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
31
33
  var fsDriver__default = /*#__PURE__*/_interopDefault(fsDriver);
32
34
  var path__default = /*#__PURE__*/_interopDefault(path);
33
35
  var sharp__default = /*#__PURE__*/_interopDefault(sharp);
36
+ var process2__default = /*#__PURE__*/_interopDefault(process2);
34
37
 
35
38
  function setDbAdapter(adapter) {
36
39
  dbAdapter = adapter;
@@ -39,8 +42,8 @@ async function loadSecrets() {
39
42
  if (dbAdapter) {
40
43
  try {
41
44
  const result = await dbAdapter.findOne({
42
- collection: "_globals",
43
- where: { slug: "system" }
45
+ collection: "_globals_system",
46
+ where: {}
44
47
  });
45
48
  if (result) {
46
49
  cachedSecrets = {
@@ -89,7 +92,7 @@ function getSessionConfig() {
89
92
  };
90
93
  }
91
94
  var dbAdapter, cachedSecrets;
92
- var init_secret = chunkG7VZBCD6_cjs.__esm({
95
+ var init_secret = chunkCZLDE2OZ_cjs.__esm({
93
96
  "src/lib/secret.ts"() {
94
97
  dbAdapter = null;
95
98
  cachedSecrets = null;
@@ -98,7 +101,7 @@ var init_secret = chunkG7VZBCD6_cjs.__esm({
98
101
 
99
102
  // src/api/rest/auth-session.ts
100
103
  var auth_session_exports = {};
101
- chunkG7VZBCD6_cjs.__export(auth_session_exports, {
104
+ chunkCZLDE2OZ_cjs.__export(auth_session_exports, {
102
105
  SESSION_CONFIG: () => SESSION_CONFIG,
103
106
  clearSessionCookie: () => clearSessionCookie,
104
107
  createSession: () => createSession,
@@ -364,7 +367,7 @@ async function getCurrentUser(request) {
364
367
  return session;
365
368
  }
366
369
  var sessionConfig, SESSION_CONFIG, SESSION_COOKIE_NAME, storage;
367
- var init_auth_session = chunkG7VZBCD6_cjs.__esm({
370
+ var init_auth_session = chunkCZLDE2OZ_cjs.__esm({
368
371
  "src/api/rest/auth-session.ts"() {
369
372
  init_secret();
370
373
  sessionConfig = getSessionConfig();
@@ -388,14 +391,14 @@ function createAuthMiddleware(config) {
388
391
  userLookup
389
392
  } = config;
390
393
  return async function authMiddleware(req) {
391
- const apiKeyRaw = chunkIBG6V56E_cjs.extractApiKeyFromRequest(req);
394
+ const apiKeyRaw = chunk4M7X5HAB_cjs.extractApiKeyFromRequest(req);
392
395
  if (apiKeyRaw && db) {
393
- const result = await chunkIBG6V56E_cjs.validateApiKey(apiKeyRaw, db, userLookup);
396
+ const result = await chunk4M7X5HAB_cjs.validateApiKey(apiKeyRaw, db, userLookup);
394
397
  if (result.valid && result.user) {
395
398
  return {
396
399
  user: result.user,
397
400
  tenantContext: createTenantContextFromUser(result.user),
398
- apiKeyContext: chunkIBG6V56E_cjs.createApiKeyContext(result),
401
+ apiKeyContext: chunk4M7X5HAB_cjs.createApiKeyContext(result),
399
402
  status: 200,
400
403
  authType: "apikey"
401
404
  };
@@ -450,6 +453,9 @@ function createTenantContextFromUser(user) {
450
453
  };
451
454
  }
452
455
 
456
+ // src/api/rest/hono-app.ts
457
+ init_secret();
458
+
453
459
  // src/auth/security/in-memory-rate-limit.ts
454
460
  var InMemoryRateLimiter = class {
455
461
  storage = /* @__PURE__ */ new Map();
@@ -782,7 +788,7 @@ var AuditLogger = class {
782
788
  serializeLog(log) {
783
789
  const result = {
784
790
  id: log.id,
785
- timestamp: log.timestamp.toISOString(),
791
+ timestamp: new Date(log.timestamp).toISOString(),
786
792
  action: log.action,
787
793
  resource: log.resource,
788
794
  success: log.success ? "1" : "0"
@@ -957,7 +963,7 @@ var AuthRoutes = class {
957
963
  constructor(config) {
958
964
  this.authAdapter = config.redis;
959
965
  this.email = config.email;
960
- this.passwordPolicy = config.passwordPolicy || new chunkIA6AU5PI_cjs.PasswordPolicy();
966
+ this.passwordPolicy = config.passwordPolicy || new chunkY7AQK4R4_cjs.PasswordPolicy();
961
967
  this.lockout = config.lockout;
962
968
  this.rateLimiter = config.rateLimiter;
963
969
  this.auditLogger = config.auditLogger;
@@ -1177,23 +1183,19 @@ var AuthRoutes = class {
1177
1183
  }
1178
1184
  }
1179
1185
  async me(req) {
1180
- try {
1181
- const session = await getCurrentUser(req);
1182
- if (!session) {
1183
- return this.errorResponse("Not authenticated", 401);
1184
- }
1185
- const user = await this.authAdapter.findUserById(session.userId);
1186
- if (!user) {
1187
- return this.errorResponse("User not found", 404);
1188
- }
1189
- return this.jsonResponse({
1190
- success: true,
1191
- user: this.sanitizeUser(user)
1192
- });
1193
- } catch (error) {
1194
- console.error("[AuthRoutes.me] Authentication failed:", error.message);
1195
- return this.errorResponse("Authentication failed", 401);
1186
+ const session = await getCurrentUser(req);
1187
+ if (!session) {
1188
+ return this.errorResponse("Not authenticated", 401);
1196
1189
  }
1190
+ return this.jsonResponse({
1191
+ success: true,
1192
+ user: {
1193
+ id: session.userId,
1194
+ email: session.email,
1195
+ role: session.role,
1196
+ tenantId: session.tenantId
1197
+ }
1198
+ });
1197
1199
  }
1198
1200
  async changePassword(req) {
1199
1201
  const session = await getCurrentUser(req);
@@ -1303,7 +1305,7 @@ var AuthRoutes = class {
1303
1305
  }
1304
1306
  if (this.auditLogger) {
1305
1307
  await this.auditLogger.log({
1306
- action: "password_reset_request",
1308
+ action: "password_reset",
1307
1309
  userId: user.id,
1308
1310
  userEmail: user.email,
1309
1311
  resource: "auth",
@@ -1452,7 +1454,7 @@ var AuthRoutes = class {
1452
1454
  return this.errorResponse("No session", 401);
1453
1455
  }
1454
1456
  try {
1455
- const updatedSession = await (init_auth_session(), chunkG7VZBCD6_cjs.__toCommonJS(auth_session_exports)).refreshSession(sessionId, req);
1457
+ const updatedSession = await (init_auth_session(), chunkCZLDE2OZ_cjs.__toCommonJS(auth_session_exports)).refreshSession(sessionId, req);
1456
1458
  if (!updatedSession) {
1457
1459
  return this.errorResponse("Session not found", 404);
1458
1460
  }
@@ -1491,7 +1493,7 @@ var AuthRoutes = class {
1491
1493
  }
1492
1494
  };
1493
1495
  function createLocalStorage(config) {
1494
- const { uploadDir, baseUrl = "/uploads" } = config;
1496
+ const { uploadDir = path.join(process2__default.default.cwd(), "public", "uploads"), baseUrl = "/uploads" } = config;
1495
1497
  async function ensureDir(dir) {
1496
1498
  if (!fs.existsSync(dir)) {
1497
1499
  await promises.mkdir(dir, { recursive: true });
@@ -1679,150 +1681,166 @@ function createLocalStorage(config) {
1679
1681
  }
1680
1682
  };
1681
1683
  }
1682
- function extractPublicDevUrlId(url) {
1683
- if (!url) return "";
1684
- if (url.startsWith("pub-")) return url;
1685
- const match = url.match(/pub-[a-zA-Z0-9]+/i);
1686
- return match ? match[0] : "";
1687
- }
1688
- function getPublicUrl(key, config) {
1689
- const normalizedKey = key.startsWith("/") ? key.slice(1) : key;
1690
- if (config.cdnUrl) {
1691
- const cdn = config.cdnUrl.replace(/\/$/, "");
1692
- return `${cdn}/${normalizedKey}`;
1693
- }
1694
- switch (config.provider) {
1695
- case "r2": {
1696
- const pubId = extractPublicDevUrlId(config.publicDevUrl);
1697
- if (pubId) {
1698
- return `https://${pubId}.r2.dev/${normalizedKey}`;
1684
+
1685
+ // src/storage/imgix.ts
1686
+ function createImgixStorage(config) {
1687
+ const signUrl = (path3, params) => {
1688
+ if (!config.signKey) return path3;
1689
+ const signer = new TextEncoder();
1690
+ const key = signer.encode(config.signKey);
1691
+ const data = signer.encode(path3 + params.toString());
1692
+ let hash = 0;
1693
+ const combined = new Uint8Array(key.length + data.length);
1694
+ combined.set(key);
1695
+ combined.set(data, key.length);
1696
+ for (let i = 0; i < combined.length; i++) {
1697
+ hash = (hash << 5) - hash + combined[i] | 0;
1698
+ }
1699
+ params.set("s", Math.abs(hash).toString(16));
1700
+ return path3;
1701
+ };
1702
+ return {
1703
+ name: "imgix",
1704
+ displayName: "Imgix",
1705
+ supportsDynamicResize: true,
1706
+ async upload(_file, _options) {
1707
+ throw new Error(
1708
+ "Imgix is a transformation service. Use another provider for uploads."
1709
+ );
1710
+ },
1711
+ async uploadFromUrl(url, options) {
1712
+ const filename = options?.filename || url.split("/").pop() || "file";
1713
+ const response = await fetch(url);
1714
+ if (!response.ok) {
1715
+ throw new Error(`Failed to fetch: ${response.statusText}`);
1699
1716
  }
1700
- return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/${normalizedKey}`;
1717
+ const blob = await response.blob();
1718
+ new File([blob], filename, { type: blob.type });
1719
+ return {
1720
+ id: Buffer.from(url).toString("base64").slice(0, 20),
1721
+ filename,
1722
+ originalName: filename,
1723
+ mimeType: blob.type,
1724
+ size: blob.size,
1725
+ url: this.getImageUrl(url),
1726
+ thumbnailUrl: this.getImageUrl(url, {
1727
+ width: 200,
1728
+ height: 200,
1729
+ fit: "crop"
1730
+ }),
1731
+ provider: "imgix",
1732
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1733
+ };
1734
+ },
1735
+ async delete(_url) {
1736
+ },
1737
+ async rename(_oldUrl, newKey) {
1738
+ return `https://${config.domain}/${newKey}`;
1739
+ },
1740
+ getImageUrl(url, transforms) {
1741
+ const parsed = new URL(url);
1742
+ const params = new URLSearchParams(parsed.search);
1743
+ if (config.defaultParameters) {
1744
+ Object.entries(config.defaultParameters).forEach(([key, value]) => {
1745
+ if (!params.has(key)) {
1746
+ params.set(key, value);
1747
+ }
1748
+ });
1749
+ }
1750
+ if (transforms) {
1751
+ if (transforms.width) params.set("w", String(transforms.width));
1752
+ if (transforms.height) params.set("h", String(transforms.height));
1753
+ if (transforms.quality) params.set("q", String(transforms.quality));
1754
+ if (transforms.format) params.set("fm", transforms.format);
1755
+ if (transforms.fit) params.set("fit", transforms.fit);
1756
+ if (transforms.blur) params.set("blur", String(transforms.blur));
1757
+ if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
1758
+ }
1759
+ params.set("auto", "compress,format");
1760
+ parsed.pathname + "?" + params.toString();
1761
+ if (config.signKey) {
1762
+ signUrl(parsed.pathname + params.toString(), params);
1763
+ }
1764
+ return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
1765
+ },
1766
+ async generateThumbnail(file) {
1767
+ return this.getImageUrl(file.url, {
1768
+ width: 200,
1769
+ height: 200,
1770
+ fit: "crop"
1771
+ });
1772
+ },
1773
+ async list() {
1774
+ return [];
1775
+ },
1776
+ async exists(url) {
1777
+ try {
1778
+ const response = await fetch(url, { method: "HEAD" });
1779
+ return response.ok;
1780
+ } catch {
1781
+ return false;
1782
+ }
1783
+ },
1784
+ async createFolder() {
1785
+ },
1786
+ async deleteFolder() {
1701
1787
  }
1702
- case "gcs":
1703
- return `https://storage.googleapis.com/${config.bucket}/${normalizedKey}`;
1704
- case "digitalocean":
1705
- return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/${normalizedKey}`;
1706
- case "backblaze":
1707
- return `https://${config.bucket}.s3.backblazeb2.com/${normalizedKey}`;
1708
- case "wasabi":
1709
- return `https://${config.bucket}.s3.wasabisys.com/${normalizedKey}`;
1710
- case "aws":
1711
- default:
1712
- return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
1788
+ };
1789
+ }
1790
+
1791
+ // src/storage/bunny.ts
1792
+ function getUrl(key, config) {
1793
+ if (config.cdnUrl) {
1794
+ return `${config.cdnUrl.replace(/\/$/, "")}/${key}`;
1713
1795
  }
1796
+ return `https://${config.storageZone}.b-cdn.net/${key}`;
1714
1797
  }
1715
1798
  function getUrlPrefix(config) {
1716
1799
  if (config.cdnUrl) {
1717
1800
  return config.cdnUrl.replace(/\/$/, "") + "/";
1718
1801
  }
1719
- switch (config.provider) {
1720
- case "r2": {
1721
- const pubId = extractPublicDevUrlId(config.publicDevUrl);
1722
- if (pubId) {
1723
- return `https://${pubId}.r2.dev/`;
1724
- }
1725
- return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/`;
1726
- }
1727
- case "gcs":
1728
- return `https://storage.googleapis.com/${config.bucket}/`;
1729
- case "digitalocean":
1730
- return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/`;
1731
- case "backblaze":
1732
- return `https://${config.bucket}.s3.backblazeb2.com/`;
1733
- case "wasabi":
1734
- return `https://${config.bucket}.s3.wasabisys.com/`;
1735
- case "aws":
1736
- default:
1737
- return `https://${config.bucket}.s3.${config.region}.amazonaws.com/`;
1738
- }
1739
- }
1740
- function getDisplayName(provider) {
1741
- switch (provider) {
1742
- case "r2":
1743
- return "Cloudflare R2";
1744
- case "gcs":
1745
- return "Google Cloud Storage";
1746
- case "digitalocean":
1747
- return "DigitalOcean Spaces";
1748
- case "backblaze":
1749
- return "Backblaze B2";
1750
- case "wasabi":
1751
- return "Wasabi";
1752
- case "aws":
1753
- default:
1754
- return "AWS S3";
1755
- }
1802
+ return `https://${config.storageZone}.b-cdn.net/`;
1756
1803
  }
1757
- function createS3Storage(config) {
1758
- console.log("[createS3Storage] Creating provider:", config.provider);
1759
- console.log("[createS3Storage] Credentials:", {
1760
- accessKeyId: config.accessKeyId ? "SET" : "UNDEFINED",
1761
- secretAccessKey: config.secretAccessKey ? "SET" : "UNDEFINED",
1762
- bucket: config.bucket,
1763
- accountId: config.accountId,
1764
- endpoint: config.endpoint
1765
- });
1766
- const client = new clientS3.S3Client({
1767
- region: config.region || "auto",
1768
- endpoint: config.endpoint,
1769
- credentials: {
1770
- accessKeyId: config.accessKeyId,
1771
- secretAccessKey: config.secretAccessKey
1772
- },
1773
- forcePathStyle: true,
1774
- tls: true,
1775
- // R2 requires specific SSL configuration
1776
- ...config.provider === "r2" && {
1777
- requestHandler: new (chunkG7VZBCD6_cjs.__require("@smithy/node-http-handler")).NodeHttpHandler({
1778
- connectionTimeout: 1e4,
1779
- socketTimeout: 1e4
1780
- })
1781
- }
1782
- });
1783
- const getKey = (path2) => {
1804
+ function createBunnyStorage(config) {
1805
+ const baseUrl = `https://storage.bunnycdn.com/${config.storageZone}`;
1806
+ const getKey = (path3) => {
1784
1807
  const prefix = config.prefix ? `${config.prefix}/` : "";
1785
- return `${prefix}${path2}`.replace(/\/+/g, "/");
1808
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
1786
1809
  };
1787
- const getUrl = (key) => getPublicUrl(key, config);
1788
1810
  return {
1789
- name: config.provider,
1790
- displayName: getDisplayName(config.provider),
1811
+ name: "bunny",
1812
+ displayName: "Bunny.net Storage",
1791
1813
  supportsDynamicResize: true,
1792
1814
  async upload(file, options) {
1793
1815
  const key = getKey(
1794
1816
  `${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
1795
1817
  );
1796
1818
  const buffer = Buffer.from(await file.arrayBuffer());
1797
- await client.send(
1798
- new clientS3.PutObjectCommand({
1799
- Bucket: config.bucket,
1800
- Key: key,
1801
- Body: buffer,
1802
- ContentType: file.type,
1803
- Metadata: options?.metadata
1804
- })
1805
- );
1806
- const head = await client.send(
1807
- new clientS3.HeadObjectCommand({
1808
- Bucket: config.bucket,
1809
- Key: key
1810
- })
1811
- );
1819
+ const response = await fetch(`${baseUrl}/${key}`, {
1820
+ method: "PUT",
1821
+ headers: {
1822
+ AccessKey: config.apiKey,
1823
+ "Content-Type": file.type
1824
+ },
1825
+ body: buffer
1826
+ });
1827
+ if (!response.ok) {
1828
+ const errorText = await response.text();
1829
+ throw new Error(
1830
+ `Bunny.net upload failed: ${response.status} ${errorText}`
1831
+ );
1832
+ }
1812
1833
  return {
1813
1834
  id: Buffer.from(key).toString("base64url"),
1814
1835
  filename: options?.filename || file.name,
1815
1836
  originalName: file.name,
1816
1837
  mimeType: file.type,
1817
1838
  size: buffer.length,
1818
- url: getUrl(key),
1819
- thumbnailUrl: file.type.startsWith("image/") ? getUrl(key) : void 0,
1839
+ url: getUrl(key, config),
1840
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl(key, config) : void 0,
1820
1841
  folder: options?.folder,
1821
- provider: config.provider,
1822
- metadata: {
1823
- ...options?.metadata,
1824
- etag: head.ETag
1825
- },
1842
+ provider: "bunny",
1843
+ metadata: options?.metadata,
1826
1844
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1827
1845
  };
1828
1846
  },
@@ -1838,15 +1856,460 @@ function createS3Storage(config) {
1838
1856
  },
1839
1857
  async delete(url) {
1840
1858
  const key = url.replace(getUrlPrefix(config), "");
1841
- await client.send(
1842
- new clientS3.DeleteObjectCommand({
1843
- Bucket: config.bucket,
1844
- Key: key
1845
- })
1846
- );
1859
+ const response = await fetch(`${baseUrl}/${key}`, {
1860
+ method: "DELETE",
1861
+ headers: {
1862
+ AccessKey: config.apiKey
1863
+ }
1864
+ });
1865
+ if (!response.ok && response.status !== 404) {
1866
+ const errorText = await response.text();
1867
+ throw new Error(
1868
+ `Bunny.net delete failed: ${response.status} ${errorText}`
1869
+ );
1870
+ }
1847
1871
  },
1848
1872
  async rename(oldUrl, newKey) {
1849
1873
  const oldKey = oldUrl.replace(getUrlPrefix(config), "");
1874
+ const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
1875
+ const response = await fetch(`${baseUrl}/${oldKey}`, {
1876
+ method: "GET",
1877
+ headers: {
1878
+ AccessKey: config.apiKey
1879
+ }
1880
+ });
1881
+ if (!response.ok) {
1882
+ throw new Error(`Bunny.net rename failed: could not read old file`);
1883
+ }
1884
+ const content = await response.arrayBuffer();
1885
+ await fetch(`${baseUrl}/${fullPath}`, {
1886
+ method: "PUT",
1887
+ headers: {
1888
+ AccessKey: config.apiKey,
1889
+ "Content-Type": response.headers.get("Content-Type") || "application/octet-stream"
1890
+ },
1891
+ body: content
1892
+ });
1893
+ await this.delete(oldUrl);
1894
+ return getUrl(fullPath, config);
1895
+ },
1896
+ getImageUrl(url, transforms) {
1897
+ if (!transforms || Object.keys(transforms).length === 0) return url;
1898
+ const params = new URLSearchParams({ url });
1899
+ if (transforms.width) params.set("w", String(transforms.width));
1900
+ if (transforms.height) params.set("h", String(transforms.height));
1901
+ if (transforms.quality) params.set("q", String(transforms.quality));
1902
+ if (transforms.format) params.set("f", transforms.format);
1903
+ return `/api/media/resize?${params.toString()}`;
1904
+ },
1905
+ async generateThumbnail(file) {
1906
+ return this.getImageUrl(file.url, { width: 400, height: 400 });
1907
+ },
1908
+ async list(prefix) {
1909
+ const key = getKey(prefix || "");
1910
+ const response = await fetch(`${baseUrl}/${key}`, {
1911
+ method: "GET",
1912
+ headers: {
1913
+ AccessKey: config.apiKey
1914
+ }
1915
+ });
1916
+ if (!response.ok) {
1917
+ const errorText = await response.text();
1918
+ throw new Error(
1919
+ `Bunny.net list failed: ${response.status} ${errorText}`
1920
+ );
1921
+ }
1922
+ const items = await response.json();
1923
+ return items.map((item) => ({
1924
+ id: Buffer.from(item.ObjectName || "").toString("base64url"),
1925
+ filename: item.ObjectName?.split("/").pop() || "",
1926
+ originalName: item.ObjectName?.split("/").pop() || "",
1927
+ mimeType: "application/octet-stream",
1928
+ size: item.Length || 0,
1929
+ url: getUrl(item.ObjectName || "", config),
1930
+ provider: "bunny",
1931
+ createdAt: item.LastChanged || (/* @__PURE__ */ new Date()).toISOString()
1932
+ }));
1933
+ },
1934
+ async exists(url) {
1935
+ const key = url.replace(getUrlPrefix(config), "");
1936
+ const response = await fetch(`${baseUrl}/${key}`, {
1937
+ method: "HEAD",
1938
+ headers: {
1939
+ AccessKey: config.apiKey
1940
+ }
1941
+ });
1942
+ return response.ok;
1943
+ },
1944
+ async createFolder(folder) {
1945
+ const key = getKey(`${folder}/`);
1946
+ await fetch(`${baseUrl}/${key}`, {
1947
+ method: "PUT",
1948
+ headers: {
1949
+ AccessKey: config.apiKey,
1950
+ "Content-Length": "0"
1951
+ },
1952
+ body: ""
1953
+ });
1954
+ },
1955
+ async deleteFolder(folder) {
1956
+ const key = getKey(`${folder}/`);
1957
+ await fetch(`${baseUrl}/${key}`, {
1958
+ method: "DELETE",
1959
+ headers: {
1960
+ AccessKey: config.apiKey
1961
+ }
1962
+ });
1963
+ }
1964
+ };
1965
+ }
1966
+ var StorageProviderRegistry = class {
1967
+ providers = /* @__PURE__ */ new Map();
1968
+ providerToPlugin = /* @__PURE__ */ new Map();
1969
+ disabledPlugins = /* @__PURE__ */ new Set();
1970
+ constructor() {
1971
+ this.registerLocal();
1972
+ this.registerImgix();
1973
+ this.registerBunny();
1974
+ }
1975
+ registerLocal() {
1976
+ this.register({
1977
+ type: "local",
1978
+ displayName: "Local Server",
1979
+ configKey: "local",
1980
+ configFields: [
1981
+ {
1982
+ name: "uploadDir",
1983
+ type: "text",
1984
+ label: "Upload Directory",
1985
+ defaultValue: "./public/uploads"
1986
+ },
1987
+ {
1988
+ name: "baseUrl",
1989
+ type: "text",
1990
+ label: "Base URL",
1991
+ defaultValue: "/uploads"
1992
+ }
1993
+ ],
1994
+ extractConfig: (sc, key) => sc[key] || {},
1995
+ extractRawConfig: (c) => {
1996
+ const localConfig = c?.local || c;
1997
+ const savedUploadDir = (localConfig?.uploadDir || "").trim();
1998
+ let uploadDir;
1999
+ if (savedUploadDir) {
2000
+ if (path__default.default.isAbsolute(savedUploadDir)) {
2001
+ uploadDir = savedUploadDir;
2002
+ } else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
2003
+ uploadDir = path__default.default.resolve(process.cwd(), savedUploadDir);
2004
+ } else {
2005
+ uploadDir = path__default.default.join(process.cwd(), "public", savedUploadDir);
2006
+ }
2007
+ } else {
2008
+ uploadDir = path__default.default.join(process.cwd(), "public", "uploads");
2009
+ }
2010
+ const savedBaseUrl = (localConfig?.baseUrl || "").trim();
2011
+ let baseUrl;
2012
+ if (savedBaseUrl) {
2013
+ baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
2014
+ } else {
2015
+ baseUrl = "/uploads";
2016
+ }
2017
+ return { uploadDir, baseUrl };
2018
+ },
2019
+ factory: (c) => createLocalStorage(c)
2020
+ });
2021
+ }
2022
+ registerImgix() {
2023
+ this.register({
2024
+ type: "imgix",
2025
+ displayName: "Imgix",
2026
+ configKey: "imgix",
2027
+ configFields: [
2028
+ { name: "domain", type: "text", label: "Domain", required: true },
2029
+ { name: "signKey", type: "password", label: "Sign Key" }
2030
+ ],
2031
+ extractConfig: (sc, key) => sc[key] || {},
2032
+ extractRawConfig: (c) => c?.imgix || c,
2033
+ factory: (c) => createImgixStorage(c)
2034
+ });
2035
+ }
2036
+ registerBunny() {
2037
+ this.register({
2038
+ type: "bunny",
2039
+ displayName: "Bunny.net",
2040
+ configKey: "bunny",
2041
+ configFields: [
2042
+ {
2043
+ name: "storageZone",
2044
+ type: "text",
2045
+ label: "Storage Zone",
2046
+ required: true
2047
+ },
2048
+ { name: "apiKey", type: "password", label: "API Key", required: true },
2049
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
2050
+ { name: "prefix", type: "text", label: "Path Prefix" }
2051
+ ],
2052
+ extractConfig: (sc, key) => sc[key] || {},
2053
+ extractRawConfig: (c) => c?.bunny || c,
2054
+ factory: (c) => createBunnyStorage(c)
2055
+ });
2056
+ }
2057
+ register(registration) {
2058
+ if (this.providers.has(registration.type)) {
2059
+ console.warn(
2060
+ `[StorageRegistry] Provider "${registration.type}" already registered, skipping`
2061
+ );
2062
+ return;
2063
+ }
2064
+ if (registration.pluginName) {
2065
+ this.providerToPlugin.set(registration.type, registration.pluginName);
2066
+ }
2067
+ this.providers.set(registration.type, registration);
2068
+ }
2069
+ unregister(type) {
2070
+ this.providers.delete(type);
2071
+ this.providerToPlugin.delete(type);
2072
+ }
2073
+ get(type) {
2074
+ return this.providers.get(type);
2075
+ }
2076
+ getAll() {
2077
+ return Array.from(this.providers.values());
2078
+ }
2079
+ getAllAvailable(isPluginEnabled) {
2080
+ const all = this.getAll();
2081
+ if (!isPluginEnabled) return all;
2082
+ return all.filter((p) => {
2083
+ if (!p.pluginName) return true;
2084
+ return isPluginEnabled(p.pluginName);
2085
+ });
2086
+ }
2087
+ has(type) {
2088
+ return this.providers.has(type);
2089
+ }
2090
+ async resolve(type, storageConfig) {
2091
+ const reg = this.providers.get(type);
2092
+ if (!reg) {
2093
+ throw new Error(
2094
+ `Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
2095
+ );
2096
+ }
2097
+ if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
2098
+ throw new Error(
2099
+ `Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
2100
+ );
2101
+ }
2102
+ const configKey = reg.configKey || type;
2103
+ const config = reg.extractConfig(storageConfig, configKey);
2104
+ return reg.factory(config);
2105
+ }
2106
+ async resolveWithConfig(type, config) {
2107
+ const reg = this.providers.get(type);
2108
+ if (!reg) {
2109
+ throw new Error(
2110
+ `Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
2111
+ );
2112
+ }
2113
+ if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
2114
+ throw new Error(
2115
+ `Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
2116
+ );
2117
+ }
2118
+ const providerConfig = reg.extractRawConfig(config);
2119
+ return reg.factory(providerConfig);
2120
+ }
2121
+ getProviderPluginName(type) {
2122
+ return this.providerToPlugin.get(type);
2123
+ }
2124
+ getAllPluginNames() {
2125
+ return Array.from(new Set(this.providerToPlugin.values()));
2126
+ }
2127
+ setPluginEnabled(name, enabled) {
2128
+ if (enabled) {
2129
+ this.disabledPlugins.delete(name);
2130
+ } else {
2131
+ this.disabledPlugins.add(name);
2132
+ }
2133
+ }
2134
+ isPluginEnabled(name) {
2135
+ return !this.disabledPlugins.has(name);
2136
+ }
2137
+ };
2138
+ var instance = null;
2139
+ function getDefaultRegistry() {
2140
+ if (!instance) {
2141
+ instance = new StorageProviderRegistry();
2142
+ }
2143
+ return instance;
2144
+ }
2145
+ function extractPublicDevUrlId(url) {
2146
+ if (!url) return "";
2147
+ if (url.startsWith("pub-")) return url;
2148
+ const match = url.match(/pub-[a-zA-Z0-9]+/i);
2149
+ return match ? match[0] : "";
2150
+ }
2151
+ function getPublicUrl(key, config) {
2152
+ const normalizedKey = key.startsWith("/") ? key.slice(1) : key;
2153
+ if (config.cdnUrl) {
2154
+ const cdn = config.cdnUrl.replace(/\/$/, "");
2155
+ return `${cdn}/${normalizedKey}`;
2156
+ }
2157
+ switch (config.provider) {
2158
+ case "r2": {
2159
+ const pubId = extractPublicDevUrlId(config.publicDevUrl);
2160
+ if (pubId) {
2161
+ return `https://${pubId}.r2.dev/${normalizedKey}`;
2162
+ }
2163
+ return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/${normalizedKey}`;
2164
+ }
2165
+ case "gcs":
2166
+ return `https://storage.googleapis.com/${config.bucket}/${normalizedKey}`;
2167
+ case "digitalocean":
2168
+ return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/${normalizedKey}`;
2169
+ case "backblaze":
2170
+ return `https://${config.bucket}.s3.backblazeb2.com/${normalizedKey}`;
2171
+ case "wasabi":
2172
+ return `https://${config.bucket}.s3.wasabisys.com/${normalizedKey}`;
2173
+ case "aws":
2174
+ default:
2175
+ return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
2176
+ }
2177
+ }
2178
+ function getUrlPrefix2(config) {
2179
+ if (config.cdnUrl) {
2180
+ return config.cdnUrl.replace(/\/$/, "") + "/";
2181
+ }
2182
+ switch (config.provider) {
2183
+ case "r2": {
2184
+ const pubId = extractPublicDevUrlId(config.publicDevUrl);
2185
+ if (pubId) {
2186
+ return `https://${pubId}.r2.dev/`;
2187
+ }
2188
+ return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/`;
2189
+ }
2190
+ case "gcs":
2191
+ return `https://storage.googleapis.com/${config.bucket}/`;
2192
+ case "digitalocean":
2193
+ return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/`;
2194
+ case "backblaze":
2195
+ return `https://${config.bucket}.s3.backblazeb2.com/`;
2196
+ case "wasabi":
2197
+ return `https://${config.bucket}.s3.wasabisys.com/`;
2198
+ case "aws":
2199
+ default:
2200
+ return `https://${config.bucket}.s3.${config.region}.amazonaws.com/`;
2201
+ }
2202
+ }
2203
+ function getDisplayName(provider) {
2204
+ switch (provider) {
2205
+ case "r2":
2206
+ return "Cloudflare R2";
2207
+ case "gcs":
2208
+ return "Google Cloud Storage";
2209
+ case "digitalocean":
2210
+ return "DigitalOcean Spaces";
2211
+ case "backblaze":
2212
+ return "Backblaze B2";
2213
+ case "wasabi":
2214
+ return "Wasabi";
2215
+ case "aws":
2216
+ default:
2217
+ return "AWS S3";
2218
+ }
2219
+ }
2220
+ function createS3Storage(config) {
2221
+ console.log("[createS3Storage] Creating provider:", config.provider);
2222
+ console.log("[createS3Storage] Credentials:", {
2223
+ accessKeyId: config.accessKeyId ? "SET" : "UNDEFINED",
2224
+ secretAccessKey: config.secretAccessKey ? "SET" : "UNDEFINED",
2225
+ bucket: config.bucket,
2226
+ accountId: config.accountId,
2227
+ endpoint: config.endpoint
2228
+ });
2229
+ const client = new clientS3.S3Client({
2230
+ region: config.region || "auto",
2231
+ endpoint: config.endpoint,
2232
+ credentials: {
2233
+ accessKeyId: config.accessKeyId,
2234
+ secretAccessKey: config.secretAccessKey
2235
+ },
2236
+ forcePathStyle: true,
2237
+ tls: true,
2238
+ // R2 requires specific SSL configuration
2239
+ ...config.provider === "r2" && {
2240
+ requestHandler: new nodeHttpHandler.NodeHttpHandler({
2241
+ connectionTimeout: 1e4,
2242
+ socketTimeout: 1e4
2243
+ })
2244
+ }
2245
+ });
2246
+ const getKey = (path3) => {
2247
+ const prefix = config.prefix ? `${config.prefix}/` : "";
2248
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
2249
+ };
2250
+ const getUrl2 = (key) => getPublicUrl(key, config);
2251
+ return {
2252
+ name: config.provider,
2253
+ displayName: getDisplayName(config.provider),
2254
+ supportsDynamicResize: true,
2255
+ async upload(file, options) {
2256
+ const key = getKey(
2257
+ `${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
2258
+ );
2259
+ const buffer = Buffer.from(await file.arrayBuffer());
2260
+ await client.send(
2261
+ new clientS3.PutObjectCommand({
2262
+ Bucket: config.bucket,
2263
+ Key: key,
2264
+ Body: buffer,
2265
+ ContentType: file.type,
2266
+ Metadata: options?.metadata
2267
+ })
2268
+ );
2269
+ const head = await client.send(
2270
+ new clientS3.HeadObjectCommand({
2271
+ Bucket: config.bucket,
2272
+ Key: key
2273
+ })
2274
+ );
2275
+ return {
2276
+ id: Buffer.from(key).toString("base64url"),
2277
+ filename: options?.filename || file.name,
2278
+ originalName: file.name,
2279
+ mimeType: file.type,
2280
+ size: buffer.length,
2281
+ url: getUrl2(key),
2282
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
2283
+ folder: options?.folder,
2284
+ provider: config.provider,
2285
+ metadata: {
2286
+ ...options?.metadata,
2287
+ etag: head.ETag
2288
+ },
2289
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2290
+ };
2291
+ },
2292
+ async uploadFromUrl(url) {
2293
+ const response = await fetch(url);
2294
+ if (!response.ok) {
2295
+ throw new Error(`Failed to fetch: ${response.statusText}`);
2296
+ }
2297
+ const blob = await response.blob();
2298
+ const filename = url.split("/").pop() || "file";
2299
+ const file = new File([blob], filename, { type: blob.type });
2300
+ return this.upload(file);
2301
+ },
2302
+ async delete(url) {
2303
+ const key = url.replace(getUrlPrefix2(config), "");
2304
+ await client.send(
2305
+ new clientS3.DeleteObjectCommand({
2306
+ Bucket: config.bucket,
2307
+ Key: key
2308
+ })
2309
+ );
2310
+ },
2311
+ async rename(oldUrl, newKey) {
2312
+ const oldKey = oldUrl.replace(getUrlPrefix2(config), "");
1850
2313
  const newKeyWithPrefix = config.prefix ? `${config.prefix}/${newKey}` : newKey;
1851
2314
  await client.send(
1852
2315
  new clientS3.CopyObjectCommand({
@@ -1861,7 +2324,7 @@ function createS3Storage(config) {
1861
2324
  Key: oldKey
1862
2325
  })
1863
2326
  );
1864
- return getUrl(newKeyWithPrefix);
2327
+ return getUrl2(newKeyWithPrefix);
1865
2328
  },
1866
2329
  getImageUrl(url, transforms) {
1867
2330
  if (!transforms || Object.keys(transforms).length === 0) return url;
@@ -1889,14 +2352,14 @@ function createS3Storage(config) {
1889
2352
  originalName: item.Key?.split("/").pop() || "",
1890
2353
  mimeType: "application/octet-stream",
1891
2354
  size: item.Size || 0,
1892
- url: getUrl(item.Key || ""),
2355
+ url: getUrl2(item.Key || ""),
1893
2356
  provider: config.provider,
1894
2357
  createdAt: item.LastModified?.toISOString() || (/* @__PURE__ */ new Date()).toISOString()
1895
2358
  }));
1896
2359
  },
1897
2360
  async exists(url) {
1898
2361
  try {
1899
- const key = url.replace(getUrlPrefix(config), "");
2362
+ const key = url.replace(getUrlPrefix2(config), "");
1900
2363
  await client.send(
1901
2364
  new clientS3.HeadObjectCommand({
1902
2365
  Bucket: config.bucket,
@@ -1956,9 +2419,9 @@ function createS3Storage(config) {
1956
2419
  function createCloudinaryStorage(config) {
1957
2420
  const getBaseUrl = () => `https://api.cloudinary.com/v1_1/${config.cloudName}/upload`;
1958
2421
  const generateSignature = async (params) => {
1959
- const crypto3 = await import('crypto');
2422
+ const crypto4 = await import('crypto');
1960
2423
  const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
1961
- return crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2424
+ return crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
1962
2425
  };
1963
2426
  return {
1964
2427
  name: "cloudinary",
@@ -2060,8 +2523,8 @@ function createCloudinaryStorage(config) {
2060
2523
  public_id: publicId
2061
2524
  };
2062
2525
  const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
2063
- const crypto3 = await import('crypto');
2064
- const signature = crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2526
+ const crypto4 = await import('crypto');
2527
+ const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2065
2528
  const deleteUrl = `https://api.cloudinary.com/v1_1/${config.cloudName}/image/destroy`;
2066
2529
  const formData = new FormData();
2067
2530
  formData.append("public_id", publicId);
@@ -2112,8 +2575,8 @@ function createCloudinaryStorage(config) {
2112
2575
  timestamp: String(timestamp)
2113
2576
  };
2114
2577
  const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
2115
- const crypto3 = await import('crypto');
2116
- const signature = crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2578
+ const crypto4 = await import('crypto');
2579
+ const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2117
2580
  const formData = new FormData();
2118
2581
  formData.append("from_public_id", publicIdWithoutVersion);
2119
2582
  formData.append("to_public_id", newPublicId);
@@ -2131,140 +2594,38 @@ function createCloudinaryStorage(config) {
2131
2594
  const data = await response.json();
2132
2595
  console.log("[Cloudinary rename] Success:", data.secure_url);
2133
2596
  return data.secure_url;
2134
- } else {
2135
- const error = await response.json();
2136
- console.warn("[Cloudinary rename] Failed:", error);
2137
- const versionStr = version ? `/${version}/` : "/";
2138
- return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
2139
- }
2140
- } catch (e) {
2141
- console.warn("[Cloudinary rename] Error:", e.message);
2142
- const versionStr = version ? `/${version}/` : "/";
2143
- return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
2144
- }
2145
- },
2146
- getImageUrl(url, transforms) {
2147
- if (!transforms) return url;
2148
- const parts = url.split("/upload/");
2149
- if (parts.length !== 2) return url;
2150
- const transformationArr = [];
2151
- if (transforms.width) transformationArr.push(`w_${transforms.width}`);
2152
- if (transforms.height) transformationArr.push(`h_${transforms.height}`);
2153
- if (transforms.fit) {
2154
- const fitMap = {
2155
- crop: "c_fill",
2156
- clip: "c_fit",
2157
- scale: "c_scale",
2158
- fill: "c_fill"
2159
- };
2160
- transformationArr.push(fitMap[transforms.fit] || "c_limit");
2161
- }
2162
- if (transforms.quality) transformationArr.push(`q_${transforms.quality}`);
2163
- if (transforms.format) transformationArr.push(`f_${transforms.format}`);
2164
- const transformationStr = transformationArr.join(",");
2165
- return `${parts[0]}/upload/${transformationStr}/${parts[1]}`;
2166
- },
2167
- async generateThumbnail(file) {
2168
- return this.getImageUrl(file.url, {
2169
- width: 200,
2170
- height: 200,
2171
- fit: "crop"
2172
- });
2173
- },
2174
- async list() {
2175
- return [];
2176
- },
2177
- async exists(url) {
2178
- const response = await fetch(url, { method: "HEAD" });
2179
- return response.ok;
2180
- },
2181
- async createFolder() {
2182
- },
2183
- async deleteFolder() {
2184
- }
2185
- };
2186
- }
2187
-
2188
- // src/storage/imgix.ts
2189
- function createImgixStorage(config) {
2190
- const signUrl = (path2, params) => {
2191
- if (!config.signKey) return path2;
2192
- const signer = new TextEncoder();
2193
- const key = signer.encode(config.signKey);
2194
- const data = signer.encode(path2 + params.toString());
2195
- let hash = 0;
2196
- const combined = new Uint8Array(key.length + data.length);
2197
- combined.set(key);
2198
- combined.set(data, key.length);
2199
- for (let i = 0; i < combined.length; i++) {
2200
- hash = (hash << 5) - hash + combined[i] | 0;
2201
- }
2202
- params.set("s", Math.abs(hash).toString(16));
2203
- return path2;
2204
- };
2205
- return {
2206
- name: "imgix",
2207
- displayName: "Imgix",
2208
- supportsDynamicResize: true,
2209
- async upload(_file, _options) {
2210
- throw new Error(
2211
- "Imgix is a transformation service. Use another provider for uploads."
2212
- );
2213
- },
2214
- async uploadFromUrl(url, options) {
2215
- const filename = options?.filename || url.split("/").pop() || "file";
2216
- const response = await fetch(url);
2217
- if (!response.ok) {
2218
- throw new Error(`Failed to fetch: ${response.statusText}`);
2597
+ } else {
2598
+ const error = await response.json();
2599
+ console.warn("[Cloudinary rename] Failed:", error);
2600
+ const versionStr = version ? `/${version}/` : "/";
2601
+ return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
2602
+ }
2603
+ } catch (e) {
2604
+ console.warn("[Cloudinary rename] Error:", e.message);
2605
+ const versionStr = version ? `/${version}/` : "/";
2606
+ return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
2219
2607
  }
2220
- const blob = await response.blob();
2221
- new File([blob], filename, { type: blob.type });
2222
- return {
2223
- id: Buffer.from(url).toString("base64").slice(0, 20),
2224
- filename,
2225
- originalName: filename,
2226
- mimeType: blob.type,
2227
- size: blob.size,
2228
- url: this.getImageUrl(url),
2229
- thumbnailUrl: this.getImageUrl(url, {
2230
- width: 200,
2231
- height: 200,
2232
- fit: "crop"
2233
- }),
2234
- provider: "imgix",
2235
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2236
- };
2237
- },
2238
- async delete(_url) {
2239
- },
2240
- async rename(_oldUrl, newKey) {
2241
- return `https://${config.domain}/${newKey}`;
2242
2608
  },
2243
2609
  getImageUrl(url, transforms) {
2244
- const parsed = new URL(url);
2245
- const params = new URLSearchParams(parsed.search);
2246
- if (config.defaultParameters) {
2247
- Object.entries(config.defaultParameters).forEach(([key, value]) => {
2248
- if (!params.has(key)) {
2249
- params.set(key, value);
2250
- }
2251
- });
2252
- }
2253
- if (transforms) {
2254
- if (transforms.width) params.set("w", String(transforms.width));
2255
- if (transforms.height) params.set("h", String(transforms.height));
2256
- if (transforms.quality) params.set("q", String(transforms.quality));
2257
- if (transforms.format) params.set("fm", transforms.format);
2258
- if (transforms.fit) params.set("fit", transforms.fit);
2259
- if (transforms.blur) params.set("blur", String(transforms.blur));
2260
- if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
2261
- }
2262
- params.set("auto", "compress,format");
2263
- parsed.pathname + "?" + params.toString();
2264
- if (config.signKey) {
2265
- signUrl(parsed.pathname + params.toString(), params);
2610
+ if (!transforms) return url;
2611
+ const parts = url.split("/upload/");
2612
+ if (parts.length !== 2) return url;
2613
+ const transformationArr = [];
2614
+ if (transforms.width) transformationArr.push(`w_${transforms.width}`);
2615
+ if (transforms.height) transformationArr.push(`h_${transforms.height}`);
2616
+ if (transforms.fit) {
2617
+ const fitMap = {
2618
+ crop: "c_fill",
2619
+ clip: "c_fit",
2620
+ scale: "c_scale",
2621
+ fill: "c_fill"
2622
+ };
2623
+ transformationArr.push(fitMap[transforms.fit] || "c_limit");
2266
2624
  }
2267
- return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
2625
+ if (transforms.quality) transformationArr.push(`q_${transforms.quality}`);
2626
+ if (transforms.format) transformationArr.push(`f_${transforms.format}`);
2627
+ const transformationStr = transformationArr.join(",");
2628
+ return `${parts[0]}/upload/${transformationStr}/${parts[1]}`;
2268
2629
  },
2269
2630
  async generateThumbnail(file) {
2270
2631
  return this.getImageUrl(file.url, {
@@ -2277,12 +2638,8 @@ function createImgixStorage(config) {
2277
2638
  return [];
2278
2639
  },
2279
2640
  async exists(url) {
2280
- try {
2281
- const response = await fetch(url, { method: "HEAD" });
2282
- return response.ok;
2283
- } catch {
2284
- return false;
2285
- }
2641
+ const response = await fetch(url, { method: "HEAD" });
2642
+ return response.ok;
2286
2643
  },
2287
2644
  async createFolder() {
2288
2645
  },
@@ -2307,15 +2664,15 @@ function createFtpStorage(config) {
2307
2664
  }
2308
2665
  return client;
2309
2666
  }
2310
- const getKey = (path2) => {
2667
+ const getKey = (path3) => {
2311
2668
  const prefix = config.prefix ? `${config.prefix}/` : "";
2312
- return `${prefix}${path2}`.replace(/\/+/g, "/");
2669
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
2313
2670
  };
2314
- const getUrl = (key) => {
2671
+ const getUrl2 = (key) => {
2315
2672
  const base = config.baseUrl.replace(/\/$/, "");
2316
2673
  return `${base}/${key}`;
2317
2674
  };
2318
- const getUrlPrefix2 = () => {
2675
+ const getUrlPrefix3 = () => {
2319
2676
  const base = config.baseUrl.replace(/\/$/, "");
2320
2677
  return base + "/";
2321
2678
  };
@@ -2346,8 +2703,8 @@ function createFtpStorage(config) {
2346
2703
  originalName: file.name,
2347
2704
  mimeType: file.type,
2348
2705
  size: buffer.length,
2349
- url: getUrl(key),
2350
- thumbnailUrl: file.type.startsWith("image/") ? getUrl(key) : void 0,
2706
+ url: getUrl2(key),
2707
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
2351
2708
  folder: options?.folder,
2352
2709
  provider: config.type,
2353
2710
  metadata: options?.metadata,
@@ -2366,15 +2723,15 @@ function createFtpStorage(config) {
2366
2723
  },
2367
2724
  async delete(url) {
2368
2725
  const ftp = await getClient();
2369
- const key = url.replace(getUrlPrefix2(), "");
2726
+ const key = url.replace(getUrlPrefix3(), "");
2370
2727
  await ftp.remove(key);
2371
2728
  },
2372
2729
  async rename(oldUrl, newKey) {
2373
2730
  const ftp = await getClient();
2374
- const oldKey = oldUrl.replace(getUrlPrefix2(), "");
2731
+ const oldKey = oldUrl.replace(getUrlPrefix3(), "");
2375
2732
  const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
2376
2733
  await ftp.rename(oldKey, fullPath);
2377
- return getUrl(fullPath);
2734
+ return getUrl2(fullPath);
2378
2735
  },
2379
2736
  getImageUrl(url, transforms) {
2380
2737
  if (!transforms || Object.keys(transforms).length === 0) return url;
@@ -2403,14 +2760,14 @@ function createFtpStorage(config) {
2403
2760
  originalName: item.name,
2404
2761
  mimeType: "application/octet-stream",
2405
2762
  size: Number(item.size) || 0,
2406
- url: getUrl(`${key}/${item.name}`.replace(/\/+/g, "/")),
2763
+ url: getUrl2(`${key}/${item.name}`.replace(/\/+/g, "/")),
2407
2764
  provider: config.type,
2408
2765
  createdAt: item.modifiedAt ? item.modifiedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
2409
2766
  }));
2410
2767
  },
2411
2768
  async exists(url) {
2412
2769
  const ftp = await getClient();
2413
- const key = url.replace(getUrlPrefix2(), "");
2770
+ const key = url.replace(getUrlPrefix3(), "");
2414
2771
  try {
2415
2772
  await ftp.size(key);
2416
2773
  return true;
@@ -2434,105 +2791,17 @@ function createFtpStorage(config) {
2434
2791
  // src/storage/index.ts
2435
2792
  async function resolveProvider(configService) {
2436
2793
  const config = configService.getStorageConfig();
2437
- switch (config.type) {
2438
- case "aws":
2439
- return createS3Storage({
2440
- provider: "aws",
2441
- bucket: config.s3.bucket || "",
2442
- region: config.s3.region || "us-east-1",
2443
- accessKeyId: config.s3.accessKeyId || "",
2444
- secretAccessKey: config.s3.secretAccessKey || "",
2445
- endpoint: config.s3.endpoint,
2446
- cdnUrl: config.s3.cdnUrl,
2447
- prefix: config.s3.prefix
2448
- });
2449
- case "r2":
2450
- return createS3Storage({
2451
- provider: "r2",
2452
- bucket: config.r2.bucket || "",
2453
- region: "auto",
2454
- accessKeyId: config.r2.accessKeyId || "",
2455
- secretAccessKey: config.r2.secretAccessKey || "",
2456
- accountId: config.r2.accountId || "",
2457
- publicDevUrl: config.r2.publicDevUrl,
2458
- endpoint: `https://${config.r2.accountId || ""}.r2.cloudflarestorage.com`,
2459
- cdnUrl: config.r2.cdnUrl,
2460
- prefix: config.r2.prefix
2461
- });
2462
- case "gcs":
2463
- return createS3Storage({
2464
- provider: "gcs",
2465
- bucket: config.gcs.bucket || "",
2466
- region: config.gcs.projectId || "auto",
2467
- accessKeyId: config.gcs.clientEmail || "",
2468
- secretAccessKey: config.gcs.privateKey || "",
2469
- cdnUrl: config.gcs.cdnUrl,
2470
- prefix: config.gcs.prefix
2471
- });
2472
- case "digitalocean":
2473
- return createS3Storage({
2474
- provider: "digitalocean",
2475
- bucket: config.digitalocean.bucket || "",
2476
- region: config.digitalocean.region || "nyc3",
2477
- accessKeyId: config.digitalocean.accessKeyId || "",
2478
- secretAccessKey: config.digitalocean.secretAccessKey || "",
2479
- endpoint: `https://${config.digitalocean.region || "nyc3"}.digitaloceanspaces.com`,
2480
- cdnUrl: config.digitalocean.cdnUrl,
2481
- prefix: config.digitalocean.prefix
2482
- });
2483
- case "backblaze":
2484
- return createS3Storage({
2485
- provider: "backblaze",
2486
- bucket: config.backblaze.bucket || "",
2487
- region: "auto",
2488
- accessKeyId: config.backblaze.applicationKeyId || "",
2489
- secretAccessKey: config.backblaze.applicationKey || "",
2490
- accountId: config.backblaze.accountId || "",
2491
- endpoint: `https://s3.backblazeb2.com`,
2492
- cdnUrl: config.backblaze.cdnUrl,
2493
- prefix: config.backblaze.prefix
2494
- });
2495
- case "wasabi":
2496
- return createS3Storage({
2497
- provider: "wasabi",
2498
- bucket: config.wasabi.bucket || "",
2499
- region: config.wasabi.region || "us-east-1",
2500
- accessKeyId: config.wasabi.accessKeyId || "",
2501
- secretAccessKey: config.wasabi.secretAccessKey || "",
2502
- endpoint: `https://s3.${config.wasabi.region || "us-east-1"}.wasabisys.com`,
2503
- cdnUrl: config.wasabi.cdnUrl,
2504
- prefix: config.wasabi.prefix
2505
- });
2506
- case "ftp":
2507
- case "sftp":
2508
- return createFtpStorage({
2509
- host: config.ftp?.host || "",
2510
- port: config.ftp?.port || 21,
2511
- user: config.ftp?.user || "",
2512
- password: config.ftp?.password || "",
2513
- secure: config.ftp?.secure || false,
2514
- baseUrl: config.ftp?.baseUrl || "",
2515
- prefix: config.ftp?.prefix,
2516
- type: "ftp"
2517
- });
2518
- case "cloudinary":
2519
- return createCloudinaryStorage({
2520
- cloudName: config.cloudinary.cloudName || "",
2521
- apiKey: config.cloudinary.apiKey || "",
2522
- apiSecret: config.cloudinary.apiSecret || "",
2523
- folder: config.cloudinary.folder
2524
- });
2525
- case "imgix":
2526
- return createImgixStorage({
2527
- domain: config.imgix.domain || "",
2528
- signKey: config.imgix.signKey
2529
- });
2530
- case "local":
2531
- default:
2532
- return createLocalStorage({
2533
- uploadDir: config.local.uploadDir || path__default.default.join(process.cwd(), "public", "uploads"),
2534
- baseUrl: config.local.baseUrl || "/uploads"
2535
- });
2794
+ const registry = getDefaultRegistry();
2795
+ try {
2796
+ return await registry.resolve(config.type, config);
2797
+ } catch (err) {
2798
+ console.warn(
2799
+ `[resolveProvider] ${err.message} \u2014 falling back to local storage`
2800
+ );
2801
+ return createLocalStorage({
2802
+ uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
2803
+ baseUrl: "/uploads"
2804
+ });
2536
2805
  }
2537
2806
  }
2538
2807
  async function resolveProviderWithConfig(config) {
@@ -2543,121 +2812,18 @@ async function resolveProviderWithConfig(config) {
2543
2812
  baseUrl: "/uploads"
2544
2813
  });
2545
2814
  }
2546
- console.log("[resolveProviderWithConfig] Creating provider:", config.type);
2547
- switch (config.type) {
2548
- case "aws":
2549
- return createS3Storage({
2550
- provider: "aws",
2551
- bucket: config.s3?.bucket || "",
2552
- region: config.s3?.region || "us-east-1",
2553
- accessKeyId: config.s3?.accessKeyId || "",
2554
- secretAccessKey: config.s3?.secretAccessKey || "",
2555
- endpoint: config.s3?.endpoint,
2556
- cdnUrl: config.s3?.cdnUrl,
2557
- prefix: config.s3?.prefix
2558
- });
2559
- case "r2":
2560
- return createS3Storage({
2561
- provider: "r2",
2562
- bucket: config.r2?.bucket || "",
2563
- region: "auto",
2564
- accessKeyId: config.r2?.accessKeyId || "",
2565
- secretAccessKey: config.r2?.secretAccessKey || "",
2566
- accountId: config.r2?.accountId || "",
2567
- publicDevUrl: config.r2?.publicDevUrl,
2568
- endpoint: `https://${config.r2?.accountId || ""}.r2.cloudflarestorage.com`,
2569
- cdnUrl: config.r2?.cdnUrl,
2570
- prefix: config.r2?.prefix
2571
- });
2572
- case "gcs":
2573
- return createS3Storage({
2574
- provider: "gcs",
2575
- bucket: config.gcs?.bucket || "",
2576
- region: config.gcs?.projectId || "auto",
2577
- accessKeyId: config.gcs?.clientEmail || "",
2578
- secretAccessKey: config.gcs?.privateKey || "",
2579
- cdnUrl: config.gcs?.cdnUrl,
2580
- prefix: config.gcs?.prefix
2581
- });
2582
- case "digitalocean":
2583
- return createS3Storage({
2584
- provider: "digitalocean",
2585
- bucket: config.digitalocean?.bucket || "",
2586
- region: config.digitalocean?.region || "nyc3",
2587
- accessKeyId: config.digitalocean?.accessKeyId || "",
2588
- secretAccessKey: config.digitalocean?.secretAccessKey || "",
2589
- cdnUrl: config.digitalocean?.cdnUrl,
2590
- prefix: config.digitalocean?.prefix
2591
- });
2592
- case "backblaze":
2593
- return createS3Storage({
2594
- provider: "backblaze",
2595
- bucket: config.backblaze?.bucket || "",
2596
- region: "auto",
2597
- accessKeyId: config.backblaze?.applicationKeyId || "",
2598
- secretAccessKey: config.backblaze?.applicationKey || "",
2599
- cdnUrl: config.backblaze?.cdnUrl,
2600
- prefix: config.backblaze?.prefix
2601
- });
2602
- case "wasabi":
2603
- return createS3Storage({
2604
- provider: "wasabi",
2605
- bucket: config.wasabi?.bucket || "",
2606
- region: config.wasabi?.region || "us-east-1",
2607
- accessKeyId: config.wasabi?.accessKeyId || "",
2608
- secretAccessKey: config.wasabi?.secretAccessKey || "",
2609
- cdnUrl: config.wasabi?.cdnUrl,
2610
- prefix: config.wasabi?.prefix
2611
- });
2612
- case "cloudinary":
2613
- return createCloudinaryStorage({
2614
- cloudName: config.cloudinary?.cloudName || "",
2615
- apiKey: config.cloudinary?.apiKey || "",
2616
- apiSecret: config.cloudinary?.apiSecret || "",
2617
- folder: config.cloudinary?.folder
2618
- });
2619
- case "ftp":
2620
- case "sftp": {
2621
- const ftpConf = config.ftp || config;
2622
- return createFtpStorage({
2623
- type: "ftp",
2624
- host: ftpConf.host || "",
2625
- port: ftpConf.port || 21,
2626
- user: ftpConf.user || "",
2627
- password: ftpConf.password || "",
2628
- secure: ftpConf.secure || false,
2629
- baseUrl: ftpConf.baseUrl || "",
2630
- prefix: ftpConf.prefix
2631
- });
2632
- }
2633
- case "local":
2634
- default: {
2635
- const localConfig = config.local || {
2636
- uploadDir: config["local.uploadDir"],
2637
- baseUrl: config["local.baseUrl"]
2638
- };
2639
- const savedUploadDir = (localConfig?.uploadDir || "").trim();
2640
- let uploadDir;
2641
- if (savedUploadDir) {
2642
- if (path__default.default.isAbsolute(savedUploadDir)) {
2643
- uploadDir = savedUploadDir;
2644
- } else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
2645
- uploadDir = path__default.default.resolve(process.cwd(), savedUploadDir);
2646
- } else {
2647
- uploadDir = path__default.default.join(process.cwd(), "public", savedUploadDir);
2648
- }
2649
- } else {
2650
- uploadDir = path__default.default.join(process.cwd(), "public", "uploads");
2651
- }
2652
- const savedBaseUrl = (localConfig?.baseUrl || "").trim();
2653
- let baseUrl;
2654
- if (savedBaseUrl) {
2655
- baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
2656
- } else {
2657
- baseUrl = "/uploads";
2658
- }
2659
- return createLocalStorage({ uploadDir, baseUrl });
2660
- }
2815
+ const type = config.type || "local";
2816
+ const registry = getDefaultRegistry();
2817
+ try {
2818
+ return await registry.resolveWithConfig(type, config);
2819
+ } catch (err) {
2820
+ console.warn(
2821
+ `[resolveProviderWithConfig] ${err.message} \u2014 falling back to local storage`
2822
+ );
2823
+ return createLocalStorage({
2824
+ uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
2825
+ baseUrl: "/uploads"
2826
+ });
2661
2827
  }
2662
2828
  }
2663
2829
  async function processImage(buffer) {
@@ -2686,17 +2852,15 @@ var MediaService = class _MediaService {
2686
2852
  this.db = db;
2687
2853
  this.storage = storage2;
2688
2854
  this.dialect = options?.dialect || "sqlite";
2689
- this.genId = options?.genId || chunk2KVHZE6O_cjs.genId;
2855
+ this.genId = options?.genId || chunkRFFSZSCL_cjs.genId;
2690
2856
  }
2691
2857
  static async init(db, options) {
2692
2858
  let storage2;
2693
2859
  if (options?.storageConfig) {
2694
2860
  storage2 = await resolveProviderWithConfig(options.storageConfig);
2695
2861
  } else {
2696
- const configService = new chunkIA6AU5PI_cjs.ConfigService(db);
2697
- if (typeof db?.select === "function") {
2698
- await configService.load();
2699
- }
2862
+ const configService = new chunkY7AQK4R4_cjs.ConfigService(db);
2863
+ await configService.load();
2700
2864
  storage2 = await resolveProvider(configService);
2701
2865
  }
2702
2866
  const service = new _MediaService(db, storage2, options);
@@ -2898,7 +3062,7 @@ var MediaService = class _MediaService {
2898
3062
  ]
2899
3063
  );
2900
3064
  } else {
2901
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3065
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
2902
3066
  const mime = storageResult.mimeType;
2903
3067
  const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("video/") ? "video" : mime.startsWith("audio/") ? "audio" : mime.startsWith("application/pdf") ? "document" : ["application/zip", "application/x-zip", "application/x-tar", "application/gzip", "application/x-7z"].includes(mime) ? "archive" : "other";
2904
3068
  await this.db.insert(mediaSchema).values({
@@ -2960,7 +3124,7 @@ var MediaService = class _MediaService {
2960
3124
  id
2961
3125
  ]);
2962
3126
  } else {
2963
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3127
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
2964
3128
  const { eq } = await import('drizzle-orm');
2965
3129
  const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
2966
3130
  if (row) item = this.rowToMedia(row);
@@ -2976,7 +3140,7 @@ var MediaService = class _MediaService {
2976
3140
  if (this.dialect === "sqlite") {
2977
3141
  await this.sqliteRun(`DELETE FROM ${this.mediaTable} WHERE id = ?`, [id]);
2978
3142
  } else {
2979
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3143
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
2980
3144
  const { eq } = await import('drizzle-orm');
2981
3145
  await this.db.delete(mediaSchema).where(eq(mediaSchema.id, id));
2982
3146
  }
@@ -2991,7 +3155,7 @@ var MediaService = class _MediaService {
2991
3155
  id
2992
3156
  ]);
2993
3157
  } else {
2994
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3158
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
2995
3159
  const { eq } = await import('drizzle-orm');
2996
3160
  const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
2997
3161
  if (row) item = this.rowToMedia(row);
@@ -3026,7 +3190,7 @@ var MediaService = class _MediaService {
3026
3190
  [...vals, this.now(), id]
3027
3191
  );
3028
3192
  } else {
3029
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3193
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
3030
3194
  const { eq } = await import('drizzle-orm');
3031
3195
  await this.db.update(mediaSchema).set({ ...updateData, updatedAt: this.now() }).where(eq(mediaSchema.id, id));
3032
3196
  }
@@ -3045,7 +3209,7 @@ var MediaService = class _MediaService {
3045
3209
  );
3046
3210
  return row2 ? this.rowToMedia(row2) : null;
3047
3211
  }
3048
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3212
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
3049
3213
  const { eq } = await import('drizzle-orm');
3050
3214
  const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
3051
3215
  return row ? this.rowToMedia(row) : null;
@@ -3093,7 +3257,7 @@ var MediaService = class _MediaService {
3093
3257
  totalPages: Math.ceil(totalDocs2 / limit)
3094
3258
  };
3095
3259
  }
3096
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3260
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
3097
3261
  const { like, or, and, asc, desc, eq, sql } = await import('drizzle-orm');
3098
3262
  const conditions = [];
3099
3263
  if (search) {
@@ -3172,7 +3336,7 @@ var MediaService = class _MediaService {
3172
3336
  );
3173
3337
  return row ? this.rowToMedia(row) : null;
3174
3338
  }
3175
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3339
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
3176
3340
  const { eq } = await import('drizzle-orm');
3177
3341
  const [updated] = await this.db.update(mediaSchema).set({ ...data, updatedAt: /* @__PURE__ */ new Date() }).where(eq(mediaSchema.id, id)).returning();
3178
3342
  return updated ? this.rowToMedia(updated) : null;
@@ -3194,7 +3358,7 @@ var MediaService = class _MediaService {
3194
3358
  );
3195
3359
  }
3196
3360
  } else {
3197
- const { media: mediaSchema } = await import('./media-XNTUFJZR.cjs');
3361
+ const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
3198
3362
  const { eq } = await import('drizzle-orm');
3199
3363
  for (const id of ids) {
3200
3364
  await this.db.update(mediaSchema).set({ ...data, updatedAt: /* @__PURE__ */ new Date() }).where(eq(mediaSchema.id, id));
@@ -3209,7 +3373,7 @@ var MediaService = class _MediaService {
3209
3373
  );
3210
3374
  return rows.map((r) => r.path).filter((f) => f && f !== "").sort();
3211
3375
  }
3212
- const { media: mediaSchema, mediaFolders: folderSchema } = await import('./media-XNTUFJZR.cjs');
3376
+ const { media: mediaSchema, mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
3213
3377
  const { eq, sql } = await import('drizzle-orm');
3214
3378
  const fromMedia = await this.db.select({ folder: mediaSchema.folder }).from(mediaSchema).groupBy(mediaSchema.folder);
3215
3379
  const fromFolders = await this.db.select({ path: folderSchema.path }).from(folderSchema);
@@ -3229,7 +3393,7 @@ var MediaService = class _MediaService {
3229
3393
  [fullPath, name, parentPath || null, now]
3230
3394
  );
3231
3395
  } else {
3232
- const { mediaFolders: folderSchema } = await import('./media-XNTUFJZR.cjs');
3396
+ const { mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
3233
3397
  await this.db.insert(folderSchema).values({
3234
3398
  path: fullPath,
3235
3399
  name,
@@ -3250,7 +3414,7 @@ var MediaService = class _MediaService {
3250
3414
  [folder, `${folder}/%`]
3251
3415
  );
3252
3416
  } else {
3253
- const { mediaFolders: folderSchema } = await import('./media-XNTUFJZR.cjs');
3417
+ const { mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
3254
3418
  const { like, or, eq } = await import('drizzle-orm');
3255
3419
  await this.db.delete(folderSchema).where(
3256
3420
  or(
@@ -3261,13 +3425,80 @@ var MediaService = class _MediaService {
3261
3425
  }
3262
3426
  }
3263
3427
  };
3264
-
3265
- // src/api/rest/hono-app.ts
3428
+ function formatZodErrors(errors) {
3429
+ return errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
3430
+ }
3431
+ function normalizeEmptyStrings(data, fields) {
3432
+ if (!data || typeof data !== "object") return;
3433
+ for (const field of fields) {
3434
+ if (!field.name || !(field.name in data)) continue;
3435
+ const val = data[field.name];
3436
+ if (val === "") {
3437
+ const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
3438
+ if (!isTextual) data[field.name] = null;
3439
+ }
3440
+ if (field.type === "tabs" && field.name && Array.isArray(field.tabs) && data[field.name] && typeof data[field.name] === "object") {
3441
+ for (const tab of field.tabs) {
3442
+ if (Array.isArray(tab.fields)) normalizeEmptyStrings(data[field.name], tab.fields);
3443
+ }
3444
+ } else if ((field.type === "group" || field.type === "collapsible") && field.name && Array.isArray(field.fields) && data[field.name] && typeof data[field.name] === "object") {
3445
+ normalizeEmptyStrings(data[field.name], field.fields);
3446
+ } else if (field.type === "array" && field.name && Array.isArray(field.fields) && Array.isArray(data[field.name])) {
3447
+ for (const item of data[field.name]) {
3448
+ if (item && typeof item === "object") normalizeEmptyStrings(item, field.fields);
3449
+ }
3450
+ } else if (field.type === "blocks" && field.name && Array.isArray(field.blocks) && Array.isArray(data[field.name])) {
3451
+ for (const item of data[field.name]) {
3452
+ if (!item || typeof item !== "object") continue;
3453
+ const blockTypeStr = item.type || item.blockType;
3454
+ if (!blockTypeStr) continue;
3455
+ const blockDef = field.blocks.find((b) => b.slug === blockTypeStr);
3456
+ if (!blockDef || !Array.isArray(blockDef.fields)) continue;
3457
+ const target = item.data && typeof item.data === "object" ? item.data : item;
3458
+ normalizeEmptyStrings(target, blockDef.fields);
3459
+ }
3460
+ }
3461
+ }
3462
+ }
3463
+ function convertRichtextFields(fields, data) {
3464
+ if (!data || typeof data !== "object") return;
3465
+ for (const field of fields) {
3466
+ if (field.type === "richtext" && field.name) {
3467
+ const val = data[field.name];
3468
+ if (typeof val === "string") {
3469
+ data[field.name] = [{ type: "paragraph", children: [{ text: val }] }];
3470
+ } else if (val && typeof val === "object" && !Array.isArray(val) && val.type === "doc" && Array.isArray(val.content)) {
3471
+ data[field.name] = val.content;
3472
+ }
3473
+ }
3474
+ if (field.type === "tabs" && field.name && Array.isArray(field.tabs) && data[field.name] && typeof data[field.name] === "object") {
3475
+ for (const tab of field.tabs) {
3476
+ if (Array.isArray(tab.fields)) convertRichtextFields(tab.fields, data[field.name]);
3477
+ }
3478
+ } else if ((field.type === "group" || field.type === "collapsible") && field.name && Array.isArray(field.fields) && data[field.name] && typeof data[field.name] === "object") {
3479
+ convertRichtextFields(field.fields, data[field.name]);
3480
+ } else if (field.type === "array" && field.name && Array.isArray(field.fields) && Array.isArray(data[field.name])) {
3481
+ for (const item of data[field.name]) {
3482
+ if (item && typeof item === "object") convertRichtextFields(field.fields, item);
3483
+ }
3484
+ } else if (field.type === "blocks" && field.name && Array.isArray(field.blocks) && Array.isArray(data[field.name])) {
3485
+ for (const item of data[field.name]) {
3486
+ if (!item || typeof item !== "object") continue;
3487
+ const blockTypeStr = item.type || item.blockType;
3488
+ if (!blockTypeStr) continue;
3489
+ const blockDef = field.blocks.find((b) => b.slug === blockTypeStr);
3490
+ if (!blockDef || !Array.isArray(blockDef.fields)) continue;
3491
+ const target = item.data && typeof item.data === "object" ? item.data : item;
3492
+ convertRichtextFields(blockDef.fields, target);
3493
+ }
3494
+ }
3495
+ }
3496
+ }
3266
3497
  var COLLECTION_EVENT_MAP = {
3267
3498
  _media: {
3268
- create: chunkIBG6V56E_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
3269
- update: chunkIBG6V56E_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
3270
- delete: chunkIBG6V56E_cjs.WEBHOOK_EVENTS.MEDIA_DELETE
3499
+ create: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
3500
+ update: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
3501
+ delete: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_DELETE
3271
3502
  }
3272
3503
  };
3273
3504
  function getWebhookEvent(collection, operation) {
@@ -3326,7 +3557,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
3326
3557
  };
3327
3558
  const isDefaultAllowed = accessLevels[defaultCollectionAccess] || false;
3328
3559
  if (accessRule) {
3329
- const allowed = await chunkR3XIBBAW_cjs.evaluateAccess(accessRule, {
3560
+ const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
3330
3561
  req,
3331
3562
  user: ctxUser,
3332
3563
  tenantID: ctxTenantID
@@ -3348,7 +3579,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
3348
3579
  const resource = collection.slug;
3349
3580
  const action = operation === "read" ? "read" : operation === "create" ? "create" : "update";
3350
3581
  const permission = `${resource}:${action}`;
3351
- if (!chunkIBG6V56E_cjs.hasApiKeyPermission(apiKeyContext.permissions, permission) && !chunkIBG6V56E_cjs.hasApiKeyPermission(apiKeyContext.permissions, `${resource}:admin`)) {
3582
+ if (!chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, permission) && !chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, `${resource}:admin`)) {
3352
3583
  return {
3353
3584
  allowed: false,
3354
3585
  error: `Missing permission: ${permission}`,
@@ -3356,20 +3587,20 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
3356
3587
  };
3357
3588
  }
3358
3589
  }
3359
- if (ctxUser) {
3590
+ if (ctxUser && !(apiKeyContext?.permissions?.length > 0)) {
3360
3591
  const resource = collection.slug;
3361
3592
  const action = operation === "read" ? "read" : operation === "create" ? "create" : operation === "update" ? "update" : "delete";
3362
3593
  const permission = `${resource}:${action}`;
3363
3594
  let rbacAllowed = false;
3364
3595
  if (ctxUser.role) {
3365
- const userHasPermission = chunkSA7NSSIQ_cjs.hasPermission(
3596
+ const userHasPermission = chunkNKPKR5BW_cjs.hasPermission(
3366
3597
  { id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
3367
3598
  permission
3368
3599
  );
3369
3600
  if (userHasPermission) {
3370
3601
  rbacAllowed = true;
3371
3602
  } else {
3372
- const adminPermission = chunkSA7NSSIQ_cjs.hasPermission(
3603
+ const adminPermission = chunkNKPKR5BW_cjs.hasPermission(
3373
3604
  { id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
3374
3605
  `${resource}:admin`
3375
3606
  );
@@ -3389,7 +3620,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
3389
3620
  async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, enablePublicAccess = true) {
3390
3621
  const accessRule = global.access?.[operation];
3391
3622
  if (accessRule) {
3392
- const allowed = await chunkR3XIBBAW_cjs.evaluateAccess(accessRule, {
3623
+ const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
3393
3624
  req,
3394
3625
  user: ctxUser,
3395
3626
  tenantID: ctxTenantID
@@ -3415,42 +3646,41 @@ async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, e
3415
3646
  return { allowed: true };
3416
3647
  }
3417
3648
  async function resolveAuthContext(req, authMw, staticUser, staticTenantID) {
3418
- if (!authMw) {
3419
- return {
3420
- user: staticUser,
3421
- tenantID: staticTenantID,
3422
- apiKeyContext: void 0
3423
- };
3424
- }
3425
- let result;
3426
- try {
3427
- result = await authMw(req);
3428
- } catch (err) {
3649
+ if (staticUser) {
3429
3650
  return {
3430
3651
  user: staticUser,
3431
3652
  tenantID: staticTenantID,
3432
- apiKeyContext: void 0
3653
+ apiKeyContext: void 0,
3654
+ authType: "static"
3433
3655
  };
3434
3656
  }
3435
- if (result.status === 401) {
3436
- return { user: void 0, tenantID: void 0, apiKeyContext: void 0 };
3657
+ if (authMw) {
3658
+ const res = await authMw(req);
3659
+ if (res.status === 200 && res.user) {
3660
+ return {
3661
+ user: res.user,
3662
+ tenantID: res.tenantContext?.tenantId,
3663
+ apiKeyContext: res.apiKeyContext,
3664
+ authType: res.authType
3665
+ };
3666
+ }
3437
3667
  }
3438
3668
  return {
3439
- user: result.user || staticUser,
3440
- tenantID: result.tenantContext?.tenantId || staticTenantID,
3441
- apiKeyContext: result.apiKeyContext,
3442
- authType: result.authType
3669
+ user: void 0,
3670
+ tenantID: void 0,
3671
+ apiKeyContext: void 0,
3672
+ authType: void 0
3443
3673
  };
3444
3674
  }
3445
3675
  function createDefaultAuthAdapter(db, rootDir) {
3446
- if (db instanceof chunk2KVHZE6O_cjs.DrizzleAdapter && db.dialect === "postgres") {
3447
- return new chunk2OL4O2TH_cjs.PostgresAuthAdapter({ db: db.client });
3676
+ if (db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter && db.dialect === "postgres") {
3677
+ return new chunk7OS7TX2Q_cjs.PostgresAuthAdapter({ db: db.client });
3448
3678
  }
3449
- if (db instanceof chunkPDYFVNUX_cjs.MongoDBAdapter) {
3450
- return new chunk4DA7QPLA_cjs.MongoDBAuthAdapter({ db: db.db });
3679
+ if (db instanceof chunkQ23GAMLE_cjs.MongoDBAdapter) {
3680
+ return new chunkGXFOGU7N_cjs.MongoDBAuthAdapter({ db: db.db });
3451
3681
  }
3452
3682
  const defaultAuthDbPath = path.resolve(rootDir, "data", "kyro.db");
3453
- return new chunkI7HHI6QV_cjs.SQLiteAuthAdapter({
3683
+ return new chunkIDVRRRAK_cjs.SQLiteAuthAdapter({
3454
3684
  path: process.env.KYRO_AUTH_DB_PATH || defaultAuthDbPath
3455
3685
  });
3456
3686
  }
@@ -3553,6 +3783,13 @@ function createHonoApp(options) {
3553
3783
  baseUrl: process.env.KYRO_BASE_URL || "http://localhost:4321",
3554
3784
  rateLimiter
3555
3785
  });
3786
+ chunkY7AQK4R4_cjs.EmailTransport.fromConfig(db).then((transport) => {
3787
+ if (transport) {
3788
+ authRoutes.email = transport;
3789
+ }
3790
+ }).catch((err) => {
3791
+ console.error("[Email] Failed to initialize transport from config:", err);
3792
+ });
3556
3793
  app.post("/api/auth/login", async (c) => authRoutes.login(c.req.raw));
3557
3794
  app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
3558
3795
  app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
@@ -3563,69 +3800,6 @@ function createHonoApp(options) {
3563
3800
  app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
3564
3801
  app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
3565
3802
  app.put("/api/auth/sessions/:id/name", async (c) => authRoutes.renameSession(c.req.raw, c.req.param("id")));
3566
- app.post("/api/graphql", async (c) => {
3567
- try {
3568
- const req = c.req.raw;
3569
- const apiKeyRaw = chunkIBG6V56E_cjs.extractApiKeyFromRequest(req);
3570
- if (apiKeyRaw && db) {
3571
- const apiKeyResult = await chunkIBG6V56E_cjs.validateApiKey(apiKeyRaw, db);
3572
- if (!apiKeyResult.valid) {
3573
- return c.json({ errors: [{ message: apiKeyResult.error || "Invalid API key" }] }, 401);
3574
- }
3575
- const apiKeyId = apiKeyResult.apiKeyId || "";
3576
- await sessionAuthAdapter?.createAuditLog({
3577
- action: "api_key_request",
3578
- userId: apiKeyResult.userId || "",
3579
- resource: "api_key",
3580
- resourceId: apiKeyId,
3581
- success: true,
3582
- metadata: {
3583
- endpoint: "/api/graphql",
3584
- method: "POST",
3585
- ip: extractIp(req)
3586
- }
3587
- });
3588
- }
3589
- const body = await req.json().catch(() => ({}));
3590
- const { query, variables } = body;
3591
- if (!query) {
3592
- return c.json({ errors: [{ message: "No query provided" }] }, 400);
3593
- }
3594
- let gqlUser;
3595
- let apiKeyCtx;
3596
- if (apiKeyRaw && db) {
3597
- const apiKeyResult = await chunkIBG6V56E_cjs.validateApiKey(apiKeyRaw, db);
3598
- if (apiKeyResult.valid && apiKeyResult.user) {
3599
- gqlUser = apiKeyResult.user;
3600
- apiKeyCtx = chunkIBG6V56E_cjs.createApiKeyContext(apiKeyResult);
3601
- }
3602
- }
3603
- const schema = chunkVJT6P4N6_cjs.buildGraphQLSchema({
3604
- registry,
3605
- db,
3606
- user: gqlUser,
3607
- req,
3608
- settings
3609
- });
3610
- const document = graphql.parse(query);
3611
- const result = await graphql.execute({
3612
- schema,
3613
- document,
3614
- variableValues: variables,
3615
- contextValue: { user: gqlUser, apiKeyContext: apiKeyCtx, req, db }
3616
- });
3617
- return c.json(result);
3618
- } catch (error) {
3619
- if (error.message?.includes("GraphQL is disabled")) {
3620
- return c.json({ errors: [{ message: "GraphQL API is disabled" }] }, 403);
3621
- }
3622
- if (error instanceof SyntaxError) {
3623
- return c.json({ errors: [{ message: "Invalid request body" }] }, 400);
3624
- }
3625
- console.error("[GraphQL] execution error:", error);
3626
- return c.json({ errors: [{ message: error.message || "GraphQL execution failed" }] }, 500);
3627
- }
3628
- });
3629
3803
  app.get("/api/auth/access", async (c) => {
3630
3804
  try {
3631
3805
  const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
@@ -3710,7 +3884,13 @@ function createHonoApp(options) {
3710
3884
  return c.json({ error: error.message }, 500);
3711
3885
  }
3712
3886
  });
3713
- const usersCollection = registry.getCollection("users");
3887
+ const usersCollection2 = typeof registry.hasCollection === "function" && registry.hasCollection("users") ? registry.getCollection("users") : (() => {
3888
+ try {
3889
+ return registry.getCollection("users");
3890
+ } catch {
3891
+ return chunkKC2GDBLS_cjs.usersCollection;
3892
+ }
3893
+ })();
3714
3894
  app.get("/api/users", async (c) => {
3715
3895
  try {
3716
3896
  const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
@@ -3720,7 +3900,7 @@ function createHonoApp(options) {
3720
3900
  tenantID
3721
3901
  );
3722
3902
  const access = await checkCollectionAccess(
3723
- usersCollection,
3903
+ usersCollection2,
3724
3904
  "read",
3725
3905
  c.req.raw,
3726
3906
  ctxUser,
@@ -3765,19 +3945,6 @@ function createHonoApp(options) {
3765
3945
  user,
3766
3946
  tenantID
3767
3947
  );
3768
- const access = await checkCollectionAccess(
3769
- usersCollection,
3770
- "read",
3771
- c.req.raw,
3772
- ctxUser,
3773
- ctxTenantID,
3774
- void 0,
3775
- enablePublicAccess,
3776
- defaultCollectionAccess
3777
- );
3778
- if (!access.allowed) {
3779
- return c.json({ error: access.error }, access.status || 403);
3780
- }
3781
3948
  const id = c.req.param("id");
3782
3949
  const found = await sessionAuthAdapter.findUserById(id);
3783
3950
  if (!found) {
@@ -3797,7 +3964,7 @@ function createHonoApp(options) {
3797
3964
  tenantID
3798
3965
  );
3799
3966
  const access = await checkCollectionAccess(
3800
- usersCollection,
3967
+ usersCollection2,
3801
3968
  "create",
3802
3969
  c.req.raw,
3803
3970
  ctxUser,
@@ -3822,8 +3989,18 @@ function createHonoApp(options) {
3822
3989
  password: body.password,
3823
3990
  name: body.name,
3824
3991
  role: body.role || "customer",
3992
+ avatar: body.avatar,
3825
3993
  tenantId: body.tenantId
3826
3994
  });
3995
+ if (ctxUser) {
3996
+ sessionAuthAdapter?.createAuditLog({
3997
+ action: "user_create",
3998
+ userId: ctxUser.id,
3999
+ resource: "users",
4000
+ resourceId: created.id,
4001
+ success: true
4002
+ });
4003
+ }
3827
4004
  return c.json(
3828
4005
  { data: created, message: "User created successfully" },
3829
4006
  201
@@ -3841,7 +4018,7 @@ function createHonoApp(options) {
3841
4018
  tenantID
3842
4019
  );
3843
4020
  const access = await checkCollectionAccess(
3844
- usersCollection,
4021
+ usersCollection2,
3845
4022
  "update",
3846
4023
  c.req.raw,
3847
4024
  ctxUser,
@@ -3863,6 +4040,9 @@ function createHonoApp(options) {
3863
4040
  if (body.name !== void 0) updateData.name = body.name;
3864
4041
  if (body.email !== void 0) updateData.email = body.email;
3865
4042
  if (body.role !== void 0) updateData.role = body.role;
4043
+ if (body.avatar !== void 0) {
4044
+ updateData.avatar = typeof body.avatar === "object" && body.avatar !== null ? body.avatar.id || String(body.avatar) : body.avatar;
4045
+ }
3866
4046
  if (body.tenantId !== void 0) updateData.tenantId = body.tenantId;
3867
4047
  if (body.emailVerified !== void 0)
3868
4048
  updateData.emailVerified = body.emailVerified;
@@ -3874,6 +4054,25 @@ function createHonoApp(options) {
3874
4054
  if (!updated) {
3875
4055
  return c.json({ error: "User update failed" }, 500);
3876
4056
  }
4057
+ if (ctxUser) {
4058
+ sessionAuthAdapter?.createAuditLog({
4059
+ action: "user_update",
4060
+ userId: ctxUser.id,
4061
+ resource: "users",
4062
+ resourceId: id,
4063
+ success: true
4064
+ });
4065
+ if (body.role && existing.role !== body.role) {
4066
+ sessionAuthAdapter?.createAuditLog({
4067
+ action: "role_change",
4068
+ userId: ctxUser.id,
4069
+ resource: "users",
4070
+ resourceId: id,
4071
+ success: true,
4072
+ metadata: { oldRole: existing.role, newRole: body.role }
4073
+ });
4074
+ }
4075
+ }
3877
4076
  return c.json({ data: updated, message: "User updated successfully" });
3878
4077
  } catch (error) {
3879
4078
  return c.json({ error: error.message }, 500);
@@ -3888,7 +4087,7 @@ function createHonoApp(options) {
3888
4087
  tenantID
3889
4088
  );
3890
4089
  const access = await checkCollectionAccess(
3891
- usersCollection,
4090
+ usersCollection2,
3892
4091
  "delete",
3893
4092
  c.req.raw,
3894
4093
  ctxUser,
@@ -3912,6 +4111,15 @@ function createHonoApp(options) {
3912
4111
  if (!deleted) {
3913
4112
  return c.json({ error: "User deletion failed" }, 500);
3914
4113
  }
4114
+ if (ctxUser) {
4115
+ sessionAuthAdapter?.createAuditLog({
4116
+ action: "user_delete",
4117
+ userId: ctxUser.id,
4118
+ resource: "users",
4119
+ resourceId: id,
4120
+ success: true
4121
+ });
4122
+ }
3915
4123
  return c.json({ data: existing, message: "User deleted successfully" });
3916
4124
  } catch (error) {
3917
4125
  return c.json({ error: error.message }, 500);
@@ -3980,8 +4188,8 @@ function createHonoApp(options) {
3980
4188
  }
3981
4189
  if (!mediaService) {
3982
4190
  try {
3983
- const dialect = db instanceof chunk2KVHZE6O_cjs.DrizzleAdapter ? db.dialect : "sqlite";
3984
- const mediaDb = dialect === "postgres" && db instanceof chunk2KVHZE6O_cjs.DrizzleAdapter ? db.client : db;
4191
+ const dialect = db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter ? db.dialect : "sqlite";
4192
+ const mediaDb = dialect === "postgres" && db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter ? db.client : db;
3985
4193
  mediaService = await MediaService.init(mediaDb, { dialect });
3986
4194
  } catch (error) {
3987
4195
  console.error("[getMedia] Init error:", error);
@@ -4068,11 +4276,11 @@ function createHonoApp(options) {
4068
4276
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4069
4277
  if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4070
4278
  const service = await getMedia();
4071
- const path2 = c.req.query("path");
4072
- if (!path2) {
4279
+ const path3 = c.req.query("path");
4280
+ if (!path3) {
4073
4281
  return c.json({ error: "Path is required" }, 400);
4074
4282
  }
4075
- await service.deleteFolder(path2);
4283
+ await service.deleteFolder(path3);
4076
4284
  return c.json({ message: "Folder deleted" });
4077
4285
  } catch (error) {
4078
4286
  console.error("[Media] delete folder error:", error);
@@ -4225,6 +4433,119 @@ function createHonoApp(options) {
4225
4433
  return c.json({ error: error.message }, 500);
4226
4434
  }
4227
4435
  });
4436
+ app.get("/api/plugins", async (c) => {
4437
+ try {
4438
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4439
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4440
+ const plugins = registry.getPlugins();
4441
+ const storageRegistry = registry.storageProviders;
4442
+ const pluginList = await Promise.all(
4443
+ plugins.map(async (p) => {
4444
+ let enabled = true;
4445
+ const pluginName = p.name;
4446
+ let states = {};
4447
+ try {
4448
+ const doc = await db.findOne({
4449
+ collection: "_globals_plugin-settings",
4450
+ where: {}
4451
+ });
4452
+ if (doc && doc.states) states = doc.states;
4453
+ } catch {
4454
+ }
4455
+ if (states[pluginName] !== void 0) {
4456
+ enabled = states[pluginName];
4457
+ }
4458
+ storageRegistry.setPluginEnabled(pluginName, enabled);
4459
+ return {
4460
+ id: pluginName,
4461
+ name: pluginName,
4462
+ version: p.version || "1.0.0",
4463
+ description: p.description || "",
4464
+ enabled,
4465
+ status: enabled ? "active" : "disabled"
4466
+ };
4467
+ })
4468
+ );
4469
+ return c.json(pluginList);
4470
+ } catch (error) {
4471
+ console.error("[Plugins] list error:", error);
4472
+ return c.json({ error: error.message }, 500);
4473
+ }
4474
+ });
4475
+ app.put("/api/plugins/:name/toggle", async (c) => {
4476
+ try {
4477
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4478
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4479
+ const pluginName = c.req.param("name");
4480
+ const storageRegistry = registry.storageProviders;
4481
+ const currentEnabled = storageRegistry.isPluginEnabled(pluginName);
4482
+ const newEnabled = !currentEnabled;
4483
+ const affectedProviders = [];
4484
+ if (!newEnabled) {
4485
+ for (const p of storageRegistry.getAll()) {
4486
+ if (p.pluginName === pluginName) {
4487
+ affectedProviders.push(p.type);
4488
+ }
4489
+ }
4490
+ }
4491
+ const force = c.req.query("force") === "1";
4492
+ if (!force && !newEnabled && affectedProviders.length > 0) {
4493
+ let activeProvider = "local";
4494
+ try {
4495
+ const row = db?.prepare?.(`SELECT provider FROM "_globals_storage-settings" LIMIT 1`)?.get();
4496
+ if (row?.provider) activeProvider = row.provider;
4497
+ } catch {
4498
+ try {
4499
+ const result = await db?.findOne?.({
4500
+ collection: "_globals_storage-settings",
4501
+ where: {}
4502
+ });
4503
+ if (result?.provider) activeProvider = result.provider;
4504
+ } catch {
4505
+ }
4506
+ }
4507
+ if (affectedProviders.includes(activeProvider)) {
4508
+ return c.json({
4509
+ error: `Cannot disable "${pluginName}" \u2014 storage provider "${activeProvider}" is currently active. Switch to Local storage first.`,
4510
+ requiresAction: true,
4511
+ activeProvider
4512
+ }, 409);
4513
+ }
4514
+ }
4515
+ try {
4516
+ let states = {};
4517
+ let docId = "global";
4518
+ const doc = await db.findOne({
4519
+ collection: "_globals_plugin-settings",
4520
+ where: {}
4521
+ });
4522
+ if (doc) {
4523
+ states = doc.states || {};
4524
+ docId = doc.id;
4525
+ }
4526
+ states[pluginName] = newEnabled;
4527
+ if (doc) {
4528
+ await db.update({
4529
+ collection: "_globals_plugin-settings",
4530
+ id: docId,
4531
+ data: { states }
4532
+ });
4533
+ } else {
4534
+ await db.create({
4535
+ collection: "_globals_plugin-settings",
4536
+ data: { states, id: "global" }
4537
+ });
4538
+ }
4539
+ } catch (e) {
4540
+ console.warn(`[Plugins] Could not persist state for "${pluginName}":`, e);
4541
+ }
4542
+ storageRegistry.setPluginEnabled(pluginName, newEnabled);
4543
+ return c.json({ name: pluginName, enabled: newEnabled });
4544
+ } catch (error) {
4545
+ console.error("[Plugins] toggle error:", error);
4546
+ return c.json({ error: error.message }, 500);
4547
+ }
4548
+ });
4228
4549
  app.get("/api/health", (c) => {
4229
4550
  return c.json({
4230
4551
  status: "ok",
@@ -4233,6 +4554,102 @@ function createHonoApp(options) {
4233
4554
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4234
4555
  });
4235
4556
  });
4557
+ app.get("/api/kyro/schema", async (c) => {
4558
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4559
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4560
+ const extractFields = (fields) => fields.map((f) => {
4561
+ const meta = {
4562
+ name: f.name,
4563
+ type: f.type,
4564
+ label: f.label,
4565
+ required: f.required ?? false,
4566
+ unique: f.unique ?? false,
4567
+ indexed: f.indexed ?? false,
4568
+ defaultValue: f.defaultValue,
4569
+ admin: f.admin ? { ...f.admin } : void 0
4570
+ };
4571
+ if (f.minLength !== void 0) meta.minLength = f.minLength;
4572
+ if (f.maxLength !== void 0) meta.maxLength = f.maxLength;
4573
+ if (f.pattern !== void 0) meta.pattern = f.pattern;
4574
+ if (f.variant !== void 0) meta.variant = f.variant;
4575
+ if (f.hasMany !== void 0) meta.hasMany = f.hasMany;
4576
+ if (f.min !== void 0) meta.min = f.min;
4577
+ if (f.max !== void 0) meta.max = f.max;
4578
+ if (f.step !== void 0) meta.step = f.step;
4579
+ if (f.integer !== void 0) meta.integer = f.integer;
4580
+ if (f.options) meta.options = f.options;
4581
+ if (f.relationTo) meta.relationTo = f.relationTo;
4582
+ if (f.maxDepth !== void 0) meta.maxDepth = f.maxDepth;
4583
+ if (f.minRows !== void 0) meta.minRows = f.minRows;
4584
+ if (f.maxRows !== void 0) meta.maxRows = f.maxRows;
4585
+ if (f.localized !== void 0) meta.localized = f.localized;
4586
+ if (f.language) meta.language = f.language;
4587
+ if (f.format) meta.format = f.format;
4588
+ if (f.allowedTypes) meta.allowedTypes = f.allowedTypes;
4589
+ if (f.maxSize) meta.maxSize = f.maxSize;
4590
+ if (f.type === "group" || f.type === "row" || f.type === "collapsible" && f.fields) {
4591
+ meta.fields = extractFields(f.fields);
4592
+ }
4593
+ if (f.type === "array" && f.fields) {
4594
+ meta.fields = extractFields(f.fields);
4595
+ }
4596
+ if (f.type === "blocks" && f.blocks) {
4597
+ meta.blocks = f.blocks.map((b) => ({
4598
+ slug: b.slug,
4599
+ label: b.label,
4600
+ fields: extractFields(b.fields)
4601
+ }));
4602
+ }
4603
+ if (f.type === "tabs" && f.tabs) {
4604
+ meta.tabs = f.tabs.map((t) => ({
4605
+ label: t.label,
4606
+ name: t.name,
4607
+ fields: extractFields(t.fields)
4608
+ }));
4609
+ }
4610
+ return meta;
4611
+ });
4612
+ const data = { collections: {}, globals: {} };
4613
+ for (const col of registry.getCollections()) {
4614
+ const slug = col.slug;
4615
+ try {
4616
+ data.collections[slug] = {
4617
+ slug,
4618
+ label: col.label || slug,
4619
+ fields: extractFields(col.fields),
4620
+ jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
4621
+ createSchema: zodToJsonSchema.zodToJsonSchema(registry.getCreateZodSchema(slug), { target: "openApi3" }),
4622
+ updateSchema: zodToJsonSchema.zodToJsonSchema(registry.getUpdateZodSchema(slug), { target: "openApi3" }),
4623
+ procedures: {
4624
+ find: { collection: slug, where: "Record<string,any>", sort: "string", limit: "number", page: "number", depth: "number", select: "string[]", draft: "boolean" },
4625
+ findByID: { collection: slug, id: "string", depth: "number", select: "string[]", draft: "boolean" },
4626
+ create: { collection: slug, data: "Record<string,any>", depth: "number", select: "string[]" },
4627
+ update: { collection: slug, id: "string", data: "Record<string,any>", depth: "number", select: "string[]", baseUpdatedAt: "string" },
4628
+ delete: { collection: slug, id: "string" },
4629
+ count: { collection: slug, where: "Record<string,any>" }
4630
+ }
4631
+ };
4632
+ } catch {
4633
+ }
4634
+ }
4635
+ for (const global of registry.getGlobals()) {
4636
+ const slug = global.slug;
4637
+ try {
4638
+ data.globals[slug] = {
4639
+ slug,
4640
+ label: global.label || slug,
4641
+ fields: extractFields(global.fields),
4642
+ jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
4643
+ procedures: {
4644
+ get: {},
4645
+ update: { data: "Record<string,any>" }
4646
+ }
4647
+ };
4648
+ } catch {
4649
+ }
4650
+ }
4651
+ return c.json(data);
4652
+ });
4236
4653
  app.get("/api/collections", async (c) => {
4237
4654
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4238
4655
  if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
@@ -4248,6 +4665,129 @@ function createHonoApp(options) {
4248
4665
  }));
4249
4666
  return c.json(collections2);
4250
4667
  });
4668
+ function resolveDocField(fields, doc, fieldName) {
4669
+ if (fieldName in doc) return doc[fieldName];
4670
+ for (const field of fields) {
4671
+ if (!field.name) continue;
4672
+ if (field.type === "tabs" && field.tabs) {
4673
+ const data = doc[field.name];
4674
+ if (data && typeof data === "object" && fieldName in data) return data[fieldName];
4675
+ }
4676
+ if ((field.type === "group" || field.type === "collapsible") && field.fields) {
4677
+ const data = doc[field.name];
4678
+ if (data && typeof data === "object") {
4679
+ if (fieldName in data) return data[fieldName];
4680
+ const nested = resolveDocField(field.fields, data, fieldName);
4681
+ if (nested !== void 0) return nested;
4682
+ }
4683
+ }
4684
+ }
4685
+ return void 0;
4686
+ }
4687
+ function flattenRelationshipFields(fields) {
4688
+ const relFields = [];
4689
+ for (const field of fields) {
4690
+ if (field.type === "relationship") {
4691
+ relFields.push(field);
4692
+ } else if (field.type === "tabs" && field.tabs) {
4693
+ for (const tab of field.tabs) {
4694
+ relFields.push(...flattenRelationshipFields(tab.fields || []));
4695
+ }
4696
+ } else if ((field.type === "group" || field.type === "collapsible") && field.fields) {
4697
+ relFields.push(...flattenRelationshipFields(field.fields || []));
4698
+ }
4699
+ }
4700
+ return relFields;
4701
+ }
4702
+ function extractRelValue(value) {
4703
+ if (!value) return [];
4704
+ if (typeof value === "string") return [value];
4705
+ if (Array.isArray(value)) return value.map((v) => typeof v === "object" ? v.value ?? v : v);
4706
+ if (typeof value === "object") {
4707
+ const v = value.value ?? value;
4708
+ return typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
4709
+ }
4710
+ return [];
4711
+ }
4712
+ async function populateRelationships(docs, collection, db2, registry2) {
4713
+ if (docs.length === 0) return;
4714
+ const relFields = flattenRelationshipFields(collection.fields);
4715
+ if (relFields.length === 0) return;
4716
+ for (const relField of relFields) {
4717
+ const targetSlugs = Array.isArray(relField.relationTo) ? relField.relationTo : [relField.relationTo];
4718
+ const idsBySlug = {};
4719
+ for (const slug of targetSlugs) {
4720
+ idsBySlug[slug] = /* @__PURE__ */ new Set();
4721
+ }
4722
+ for (const doc of docs) {
4723
+ const raw = resolveDocField(collection.fields, doc, relField.name);
4724
+ const ids = extractRelValue(raw);
4725
+ for (const id of ids) {
4726
+ if (!id) continue;
4727
+ if (targetSlugs.length === 1) {
4728
+ idsBySlug[targetSlugs[0]].add(id);
4729
+ } else {
4730
+ for (const slug of targetSlugs) {
4731
+ idsBySlug[slug].add(id);
4732
+ }
4733
+ }
4734
+ }
4735
+ }
4736
+ for (const [targetSlug, idSet] of Object.entries(idsBySlug)) {
4737
+ if (idSet.size === 0) continue;
4738
+ const targetCollection = registry2.getCollection(targetSlug);
4739
+ if (!targetCollection) continue;
4740
+ const titleField = targetCollection.admin?.useAsTitle || "title";
4741
+ const idArr = Array.from(idSet);
4742
+ const relatedDocs = [];
4743
+ for (const id of idArr) {
4744
+ try {
4745
+ const relDoc = await db2.findByID({ collection: targetSlug, id, draft: true });
4746
+ if (relDoc) {
4747
+ const title = resolveDocField(targetCollection.fields, relDoc, titleField) ?? id;
4748
+ relatedDocs.push({ id, title: String(title) });
4749
+ }
4750
+ } catch {
4751
+ relatedDocs.push({ id, title: id });
4752
+ }
4753
+ }
4754
+ const titleMap = new Map(relatedDocs.map((d) => [d.id, d.title]));
4755
+ for (const doc of docs) {
4756
+ const raw = resolveDocField(collection.fields, doc, relField.name);
4757
+ if (!raw) continue;
4758
+ const setValue = (val) => {
4759
+ if (relField.name in doc) {
4760
+ doc[relField.name] = val;
4761
+ } else {
4762
+ for (const f of collection.fields) {
4763
+ if (f.type === "tabs" && f.tabs) {
4764
+ for (const tab of f.tabs) {
4765
+ if (tab.fields?.some((tf) => tf.name === relField.name)) {
4766
+ const tabData = doc[f.name];
4767
+ if (tabData && typeof tabData === "object") {
4768
+ tabData[relField.name] = val;
4769
+ }
4770
+ }
4771
+ }
4772
+ }
4773
+ }
4774
+ }
4775
+ };
4776
+ if (typeof raw === "string") {
4777
+ setValue({ id: raw, title: titleMap.get(raw) || raw });
4778
+ } else if (Array.isArray(raw)) {
4779
+ setValue(raw.map((v) => {
4780
+ const id = typeof v === "object" ? v.value ?? v.id : v;
4781
+ return { id, title: titleMap.get(id) || id };
4782
+ }));
4783
+ } else if (typeof raw === "object") {
4784
+ const id = raw.value ?? raw.id;
4785
+ setValue({ id, title: titleMap.get(id) || id });
4786
+ }
4787
+ }
4788
+ }
4789
+ }
4790
+ }
4251
4791
  app.get("/api/search", async (c) => {
4252
4792
  try {
4253
4793
  const query = c.req.query("q") || "";
@@ -4299,11 +4839,12 @@ function createHonoApp(options) {
4299
4839
  limit,
4300
4840
  tenantID: ctxTenantID
4301
4841
  });
4842
+ await populateRelationships(searchResult.docs, collection, db, registry);
4302
4843
  for (const doc of searchResult.docs) {
4303
4844
  const titleField = collection.admin?.useAsTitle || searchableFields.find(
4304
4845
  (f) => f === "title" || f === "name" || f === "heading" || f === "slug"
4305
4846
  );
4306
- const title = titleField ? doc[titleField] : doc.id;
4847
+ const title = titleField ? resolveDocField(collection.fields, doc, titleField) ?? doc.id : doc.id;
4307
4848
  results.push({
4308
4849
  collection: collection.slug,
4309
4850
  label: collection.label || collection.slug,
@@ -4324,15 +4865,13 @@ function createHonoApp(options) {
4324
4865
  });
4325
4866
  app.get("/api/keys", async (c) => {
4326
4867
  try {
4327
- const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4328
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:read")) {
4868
+ const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4869
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
4329
4870
  return c.json({ error: "Forbidden" }, 403);
4330
4871
  }
4331
4872
  const page = parseInt(c.req.query("page") || "1");
4332
4873
  const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
4333
- console.log("[ApiKeys] Querying", chunkIBG6V56E_cjs.API_KEY_COLLECTION, { page, limit });
4334
- const result = await db.find({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, where: {}, page, limit });
4335
- console.log("[ApiKeys] Result:", result);
4874
+ const result = await db.find({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, where: {}, page, limit, tenantID: ctxTenantID });
4336
4875
  const docs = (result.docs || []).map((doc) => ({
4337
4876
  id: doc.id,
4338
4877
  name: doc.name,
@@ -4350,21 +4889,21 @@ function createHonoApp(options) {
4350
4889
  app.post("/api/keys", async (c) => {
4351
4890
  try {
4352
4891
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4353
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
4892
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4354
4893
  return c.json({ error: "Forbidden" }, 403);
4355
4894
  }
4356
4895
  const body = await c.req.json();
4357
4896
  if (!body.name || typeof body.name !== "string") {
4358
4897
  return c.json({ error: "name is required" }, 400);
4359
4898
  }
4360
- const rawKey = chunkIBG6V56E_cjs.generateApiKey();
4899
+ const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
4361
4900
  const doc = await db.create({
4362
- collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION,
4901
+ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
4363
4902
  data: {
4364
4903
  userId: ctxUser.id,
4365
4904
  name: body.name,
4366
4905
  key: rawKey,
4367
- keyPrefix: chunkIBG6V56E_cjs.generateApiKeyPrefix(rawKey),
4906
+ keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
4368
4907
  permissions: Array.isArray(body.permissions) ? body.permissions : ["*"],
4369
4908
  expiresAt: body.expiresAt || null,
4370
4909
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4391,13 +4930,13 @@ function createHonoApp(options) {
4391
4930
  app.delete("/api/keys/:id", async (c) => {
4392
4931
  try {
4393
4932
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4394
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
4933
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4395
4934
  return c.json({ error: "Forbidden" }, 403);
4396
4935
  }
4397
4936
  const id = c.req.param("id");
4398
- const existing = await db.findByID({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, id });
4937
+ const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
4399
4938
  if (!existing) return c.json({ error: "API key not found" }, 404);
4400
- await db.delete({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, id });
4939
+ await db.delete({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
4401
4940
  await sessionAuthAdapter?.createAuditLog({
4402
4941
  action: "api_key_delete",
4403
4942
  userId: ctxUser.id,
@@ -4415,19 +4954,19 @@ function createHonoApp(options) {
4415
4954
  app.patch("/api/keys/:id", async (c) => {
4416
4955
  try {
4417
4956
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4418
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
4957
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4419
4958
  return c.json({ error: "Forbidden" }, 403);
4420
4959
  }
4421
4960
  const id = c.req.param("id");
4422
4961
  const body = await c.req.json();
4423
- const existing = await db.findByID({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, id });
4962
+ const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
4424
4963
  if (!existing) return c.json({ error: "API key not found" }, 404);
4425
4964
  const updateData = {};
4426
4965
  if (typeof body.name === "string" && body.name.trim()) updateData.name = body.name.trim();
4427
4966
  if (Array.isArray(body.permissions)) updateData.permissions = body.permissions;
4428
4967
  if (body.expiresAt !== void 0) updateData.expiresAt = body.expiresAt || null;
4429
4968
  if (Object.keys(updateData).length === 0) return c.json({ error: "Nothing to update" }, 400);
4430
- const updated = await db.update({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, id, data: updateData });
4969
+ const updated = await db.update({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id, data: updateData });
4431
4970
  return c.json({ ...updated, keyPrefix: existing.keyPrefix });
4432
4971
  } catch (error) {
4433
4972
  console.error("[ApiKeys] PATCH error:", error);
@@ -4437,19 +4976,19 @@ function createHonoApp(options) {
4437
4976
  app.post("/api/keys/:id/rotate", async (c) => {
4438
4977
  try {
4439
4978
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4440
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
4979
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4441
4980
  return c.json({ error: "Forbidden" }, 403);
4442
4981
  }
4443
4982
  const id = c.req.param("id");
4444
- const existing = await db.findByID({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, id });
4983
+ const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
4445
4984
  if (!existing) return c.json({ error: "API key not found" }, 404);
4446
- const rawKey = chunkIBG6V56E_cjs.generateApiKey();
4985
+ const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
4447
4986
  const updated = await db.update({
4448
- collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION,
4987
+ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
4449
4988
  id,
4450
4989
  data: {
4451
4990
  key: rawKey,
4452
- keyPrefix: chunkIBG6V56E_cjs.generateApiKeyPrefix(rawKey),
4991
+ keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
4453
4992
  lastUsedAt: null
4454
4993
  }
4455
4994
  });
@@ -4475,7 +5014,7 @@ function createHonoApp(options) {
4475
5014
  app.get("/api/webhooks", async (c) => {
4476
5015
  try {
4477
5016
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4478
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:read")) {
5017
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
4479
5018
  return c.json({ error: "Forbidden" }, 403);
4480
5019
  }
4481
5020
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4489,7 +5028,7 @@ function createHonoApp(options) {
4489
5028
  app.post("/api/webhooks", async (c) => {
4490
5029
  try {
4491
5030
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4492
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
5031
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4493
5032
  return c.json({ error: "Forbidden" }, 403);
4494
5033
  }
4495
5034
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4512,7 +5051,7 @@ function createHonoApp(options) {
4512
5051
  app.get("/api/webhooks/:id", async (c) => {
4513
5052
  try {
4514
5053
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4515
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:read")) {
5054
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
4516
5055
  return c.json({ error: "Forbidden" }, 403);
4517
5056
  }
4518
5057
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4528,7 +5067,7 @@ function createHonoApp(options) {
4528
5067
  app.patch("/api/webhooks/:id", async (c) => {
4529
5068
  try {
4530
5069
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4531
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
5070
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4532
5071
  return c.json({ error: "Forbidden" }, 403);
4533
5072
  }
4534
5073
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4553,7 +5092,7 @@ function createHonoApp(options) {
4553
5092
  app.delete("/api/webhooks/:id", async (c) => {
4554
5093
  try {
4555
5094
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4556
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
5095
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4557
5096
  return c.json({ error: "Forbidden" }, 403);
4558
5097
  }
4559
5098
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4578,7 +5117,7 @@ function createHonoApp(options) {
4578
5117
  app.post("/api/webhooks/:id/test", async (c) => {
4579
5118
  try {
4580
5119
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4581
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:admin")) {
5120
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
4582
5121
  return c.json({ error: "Forbidden" }, 403);
4583
5122
  }
4584
5123
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4594,7 +5133,7 @@ function createHonoApp(options) {
4594
5133
  app.get("/api/webhooks/:id/history", async (c) => {
4595
5134
  try {
4596
5135
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4597
- if (!ctxUser || !chunkSA7NSSIQ_cjs.hasPermission(ctxUser, "users:read")) {
5136
+ if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
4598
5137
  return c.json({ error: "Forbidden" }, 403);
4599
5138
  }
4600
5139
  if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
@@ -4643,37 +5182,31 @@ function createHonoApp(options) {
4643
5182
  if (!access.allowed) {
4644
5183
  return c.json({ error: access.error }, access.status || 403);
4645
5184
  }
4646
- auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "GET", c.req.raw);
5185
+ if (ctxTenantID) {
5186
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5187
+ }
4647
5188
  const url = new URL(c.req.url);
4648
5189
  const page = parseInt(url.searchParams.get("page") || "1");
4649
- const limit = Math.min(
4650
- parseInt(url.searchParams.get("limit") || "10"),
4651
- 100
4652
- );
5190
+ const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 100);
4653
5191
  const sort = url.searchParams.get("sort") || void 0;
4654
5192
  const depth = parseInt(url.searchParams.get("depth") || "0");
4655
5193
  const select = url.searchParams.get("select")?.split(",") || void 0;
4656
- let where = {};
4657
- const whereParam = url.searchParams.get("where");
4658
- if (whereParam) {
4659
- try {
4660
- where = JSON.parse(whereParam);
4661
- } catch {
4662
- }
4663
- }
5194
+ const isDraftRequest = !!ctxUser;
4664
5195
  const result = await db.find({
4665
5196
  collection: slug,
4666
- where,
5197
+ where: {},
4667
5198
  sort,
4668
5199
  limit,
4669
5200
  page,
4670
5201
  depth,
4671
5202
  tenantID: ctxTenantID,
4672
- select
5203
+ select,
5204
+ draft: isDraftRequest
4673
5205
  });
5206
+ await populateRelationships(result.docs, collection, db, registry);
4674
5207
  return c.json(result);
4675
5208
  } catch (error) {
4676
- console.error(`[API] Error listing ${slug}:`, error);
5209
+ console.error("[API] list error:", error);
4677
5210
  return c.json({ error: error.message }, 500);
4678
5211
  }
4679
5212
  });
@@ -4698,6 +5231,9 @@ function createHonoApp(options) {
4698
5231
  return c.json({ error: access.error }, access.status || 403);
4699
5232
  }
4700
5233
  const id = c.req.param("id");
5234
+ if (!id) {
5235
+ return c.json({ error: "Missing document ID" }, 400);
5236
+ }
4701
5237
  const url = new URL(c.req.url);
4702
5238
  const compareA = url.searchParams.get("compareA");
4703
5239
  const compareB = url.searchParams.get("compareB");
@@ -4728,36 +5264,6 @@ function createHonoApp(options) {
4728
5264
  return c.json({ error: error.message }, 500);
4729
5265
  }
4730
5266
  });
4731
- app.get(`${basePath}/:id/draft`, async (c) => {
4732
- try {
4733
- const {
4734
- user: ctxUser,
4735
- tenantID: ctxTenantID,
4736
- apiKeyContext
4737
- } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4738
- const access = await checkCollectionAccess(
4739
- collection,
4740
- "read",
4741
- c.req.raw,
4742
- ctxUser,
4743
- ctxTenantID,
4744
- apiKeyContext,
4745
- enablePublicAccess,
4746
- defaultCollectionAccess
4747
- );
4748
- if (!access.allowed) {
4749
- return c.json({ error: access.error }, access.status || 403);
4750
- }
4751
- const draft = await db.findDraft({
4752
- collection: slug,
4753
- documentId: c.req.param("id"),
4754
- tenantID: ctxTenantID
4755
- });
4756
- return c.json({ data: draft });
4757
- } catch (error) {
4758
- return c.json({ error: error.message }, 500);
4759
- }
4760
- });
4761
5267
  app.put(`${basePath}/:id/draft`, async (c) => {
4762
5268
  try {
4763
5269
  const {
@@ -4781,24 +5287,39 @@ function createHonoApp(options) {
4781
5287
  const id = c.req.param("id");
4782
5288
  const body = await c.req.json();
4783
5289
  const baseUpdatedAt = readBaseUpdatedAt(body);
4784
- const data = body.data ?? omitRevisionFields(body);
4785
5290
  const originalDoc = await db.findByID({
4786
5291
  collection: slug,
4787
5292
  id,
4788
- tenantID: ctxTenantID
5293
+ tenantID: ctxTenantID,
5294
+ draft: true
4789
5295
  });
4790
5296
  if (!originalDoc) {
4791
5297
  return c.json({ error: "Document not found" }, 404);
4792
5298
  }
4793
- const draft = await db.upsertDraft({
5299
+ let finalData;
5300
+ if (body.delta) {
5301
+ finalData = { ...originalDoc, ...body.delta };
5302
+ } else {
5303
+ finalData = body.data ?? omitRevisionFields(body);
5304
+ }
5305
+ const version = await db.updateLatestVersion({
4794
5306
  collection: slug,
4795
5307
  documentId: id,
4796
- tenantID: ctxTenantID,
4797
- data,
4798
- baseUpdatedAt,
4799
- draftUpdatedAt: body.draftUpdatedAt
5308
+ data: finalData,
5309
+ status: "draft",
5310
+ tenantID: ctxTenantID
4800
5311
  });
4801
- return c.json({ data: draft, message: "Draft saved successfully" });
5312
+ if (ctxUser) {
5313
+ sessionAuthAdapter?.createAuditLog({
5314
+ action: "document_update",
5315
+ userId: ctxUser.id,
5316
+ resource: slug,
5317
+ resourceId: id,
5318
+ success: true,
5319
+ metadata: { type: "draft_save" }
5320
+ });
5321
+ }
5322
+ return c.json({ data: version, message: "Draft saved successfully" });
4802
5323
  } catch (error) {
4803
5324
  return c.json({ error: error.message }, 500);
4804
5325
  }
@@ -4823,11 +5344,16 @@ function createHonoApp(options) {
4823
5344
  if (!access.allowed) {
4824
5345
  return c.json({ error: access.error }, access.status || 403);
4825
5346
  }
4826
- await db.deleteDraft({
4827
- collection: slug,
4828
- documentId: c.req.param("id"),
4829
- tenantID: ctxTenantID
4830
- });
5347
+ if (ctxUser) {
5348
+ sessionAuthAdapter?.createAuditLog({
5349
+ action: "document_update",
5350
+ userId: ctxUser.id,
5351
+ resource: slug,
5352
+ resourceId: c.req.param("id"),
5353
+ success: true,
5354
+ metadata: { type: "draft_discard" }
5355
+ });
5356
+ }
4831
5357
  return c.json({ message: "Draft discarded successfully" });
4832
5358
  } catch (error) {
4833
5359
  return c.json({ error: error.message }, 500);
@@ -4870,12 +5396,14 @@ function createHonoApp(options) {
4870
5396
  doc = await db.findOne({
4871
5397
  collection: slug,
4872
5398
  where: { slug: id },
4873
- tenantID: ctxTenantID
5399
+ tenantID: ctxTenantID,
5400
+ draft: isDraftRequest
4874
5401
  });
4875
5402
  }
4876
5403
  if (!doc) {
4877
5404
  return c.json({ error: "Document not found" }, 404);
4878
5405
  }
5406
+ await populateRelationships([doc], collection, db, registry);
4879
5407
  return c.json({ data: doc });
4880
5408
  } catch (error) {
4881
5409
  return c.json({ error: error.message }, 500);
@@ -4901,23 +5429,81 @@ function createHonoApp(options) {
4901
5429
  if (!access.allowed) {
4902
5430
  return c.json({ error: access.error }, access.status || 403);
4903
5431
  }
5432
+ if (ctxTenantID) {
5433
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5434
+ }
4904
5435
  auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "POST", c.req.raw);
4905
5436
  const body = await c.req.json();
5437
+ let validated = body;
5438
+ normalizeEmptyStrings(validated, collection.fields);
5439
+ convertRichtextFields(collection.fields, validated);
5440
+ const hookReq = c.req.raw;
5441
+ if (collection.hooks?.beforeValidate) {
5442
+ for (const hook of collection.hooks.beforeValidate) {
5443
+ const hookResult = await hook({
5444
+ collection: slug,
5445
+ data: validated,
5446
+ req: hookReq,
5447
+ user: ctxUser,
5448
+ tenantID: ctxTenantID,
5449
+ operation: "create"
5450
+ });
5451
+ if (hookResult) Object.assign(validated, hookResult);
5452
+ }
5453
+ }
4906
5454
  const schema = registry.getCreateZodSchema(slug);
4907
- let validated;
4908
5455
  try {
4909
- validated = schema.parse(body);
5456
+ validated = schema.parse(validated);
4910
5457
  } catch (zodErr) {
4911
- return c.json({ error: "Validation failed", details: zodErr.errors }, 400);
5458
+ return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
4912
5459
  }
4913
5460
  if (collection.tenantScoped && ctxTenantID) {
4914
5461
  validated.tenantID = ctxTenantID;
4915
5462
  }
5463
+ const isDraftEnabled = collection.versions?.drafts === true;
5464
+ validated.status = isDraftEnabled ? "draft" : "published";
5465
+ if (collection.hooks?.beforeChange) {
5466
+ for (const hook of collection.hooks.beforeChange) {
5467
+ const hookResult = await hook({
5468
+ collection: slug,
5469
+ data: validated,
5470
+ req: hookReq,
5471
+ user: ctxUser,
5472
+ tenantID: ctxTenantID,
5473
+ operation: "create"
5474
+ });
5475
+ if (hookResult) Object.assign(validated, hookResult);
5476
+ }
5477
+ }
4916
5478
  const doc = await db.create({
4917
5479
  collection: slug,
4918
5480
  data: validated,
4919
5481
  tenantID: ctxTenantID
4920
5482
  });
5483
+ if (isDraftEnabled) {
5484
+ await db.createVersion({
5485
+ collection: slug,
5486
+ documentId: doc.id,
5487
+ data: validated,
5488
+ status: "draft",
5489
+ createdBy: ctxUser?.id,
5490
+ changeDescription: "Created",
5491
+ tenantID: ctxTenantID
5492
+ });
5493
+ }
5494
+ if (collection.hooks?.afterChange) {
5495
+ for (const hook of collection.hooks.afterChange) {
5496
+ await hook({
5497
+ collection: slug,
5498
+ doc,
5499
+ data: validated,
5500
+ req: hookReq,
5501
+ user: ctxUser,
5502
+ tenantID: ctxTenantID,
5503
+ operation: "create"
5504
+ });
5505
+ }
5506
+ }
4921
5507
  if (webhookService) {
4922
5508
  webhookService.trigger(getWebhookEvent(slug, "create"), {
4923
5509
  collection: slug,
@@ -4927,11 +5513,20 @@ function createHonoApp(options) {
4927
5513
  tenantId: ctxTenantID
4928
5514
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
4929
5515
  }
5516
+ if (ctxUser) {
5517
+ sessionAuthAdapter?.createAuditLog({
5518
+ action: "document_create",
5519
+ userId: ctxUser.id,
5520
+ resource: slug,
5521
+ resourceId: doc.id,
5522
+ success: true
5523
+ });
5524
+ }
4930
5525
  return c.json({ data: doc, message: "Created successfully" }, 201);
4931
5526
  } catch (error) {
4932
5527
  if (error.name === "ZodError") {
4933
5528
  return c.json(
4934
- { error: "Validation failed", details: error.errors },
5529
+ { error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
4935
5530
  400
4936
5531
  );
4937
5532
  }
@@ -4961,61 +5556,92 @@ function createHonoApp(options) {
4961
5556
  const id = c.req.param("id");
4962
5557
  const body = await c.req.json();
4963
5558
  const baseUpdatedAt = readBaseUpdatedAt(body);
4964
- console.log(`[PATCH] ${slug}/${id}`, {
4965
- baseUpdatedAt,
4966
- bodyKeys: Object.keys(body),
4967
- tenantID: ctxTenantID
4968
- });
4969
- const cleaned = Object.fromEntries(
4970
- Object.entries(omitRevisionFields(body)).filter(
4971
- ([_, v]) => v !== "null" && v !== void 0
4972
- )
4973
- );
4974
- const schema = registry.getUpdateZodSchema(slug);
4975
- const validated = schema.parse(cleaned);
4976
- console.log(`[PATCH] Validated data:`, Object.keys(validated));
4977
5559
  const originalDoc = await db.findByID({
4978
5560
  collection: slug,
4979
5561
  id,
4980
5562
  tenantID: ctxTenantID,
4981
5563
  draft: true
4982
- // Always fetch current doc regardless of status
4983
5564
  });
4984
- if (originalDoc) {
4985
- console.log(`[PATCH] Original doc updatedAt:`, originalDoc.updatedAt);
4986
- }
4987
5565
  if (!originalDoc) {
4988
5566
  return c.json({ error: "Document not found" }, 404);
4989
5567
  }
4990
5568
  if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
4991
5569
  return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
4992
5570
  }
5571
+ let validated = Object.fromEntries(
5572
+ Object.entries(omitRevisionFields(body)).filter(
5573
+ ([_, v]) => v !== "null" && v !== void 0
5574
+ )
5575
+ );
5576
+ normalizeEmptyStrings(validated, collection.fields);
5577
+ convertRichtextFields(collection.fields, validated);
5578
+ const hookReq = c.req.raw;
5579
+ if (collection.hooks?.beforeValidate) {
5580
+ for (const hook of collection.hooks.beforeValidate) {
5581
+ const hookResult = await hook({
5582
+ collection: slug,
5583
+ data: validated,
5584
+ originalDoc,
5585
+ req: hookReq,
5586
+ user: ctxUser,
5587
+ tenantID: ctxTenantID,
5588
+ operation: "update"
5589
+ });
5590
+ if (hookResult) Object.assign(validated, hookResult);
5591
+ }
5592
+ }
5593
+ const schema = registry.getUpdateZodSchema(slug);
5594
+ validated = schema.parse(validated);
5595
+ if (collection.hooks?.beforeChange) {
5596
+ for (const hook of collection.hooks.beforeChange) {
5597
+ const hookResult = await hook({
5598
+ collection: slug,
5599
+ data: validated,
5600
+ originalDoc,
5601
+ req: hookReq,
5602
+ user: ctxUser,
5603
+ tenantID: ctxTenantID,
5604
+ operation: "update"
5605
+ });
5606
+ if (hookResult) Object.assign(validated, hookResult);
5607
+ }
5608
+ }
5609
+ const isDraft = c.req.header("X-Draft") === "true";
4993
5610
  const isDraftEnabled = collection.versions?.drafts === true;
4994
- const isAlreadyPublished = originalDoc._status === "published";
5611
+ const isAutosave = c.req.query("autosave") === "true";
4995
5612
  let doc;
4996
- if (isDraftEnabled && isAlreadyPublished) {
5613
+ if (isDraftEnabled && isDraft) {
4997
5614
  await db.createVersion({
4998
5615
  collection: slug,
4999
5616
  documentId: id,
5000
5617
  data: validated,
5001
5618
  status: "draft",
5619
+ autosave: isAutosave,
5002
5620
  createdBy: ctxUser?.id,
5003
- changeDescription: "Manual save (Draft)",
5621
+ changeDescription: isAutosave ? "Autosave" : "Draft saved",
5004
5622
  tenantID: ctxTenantID
5005
5623
  });
5624
+ } else if (isDraftEnabled) {
5006
5625
  await db.update({
5007
5626
  collection: slug,
5008
5627
  id,
5009
- data: { _has_draft: true },
5010
- // Keep old data, just set flag
5628
+ data: { ...validated, status: "published" },
5629
+ tenantID: ctxTenantID
5630
+ });
5631
+ await db.createVersion({
5632
+ collection: slug,
5633
+ documentId: id,
5634
+ data: validated,
5635
+ status: "published",
5636
+ createdBy: ctxUser?.id,
5637
+ changeDescription: "Published",
5011
5638
  tenantID: ctxTenantID
5012
5639
  });
5013
5640
  } else {
5014
- const saveData = isDraftEnabled ? { ...validated, _status: "draft", _has_draft: false } : validated;
5015
5641
  await db.update({
5016
5642
  collection: slug,
5017
5643
  id,
5018
- data: saveData,
5644
+ data: validated,
5019
5645
  tenantID: ctxTenantID
5020
5646
  });
5021
5647
  }
@@ -5025,24 +5651,19 @@ function createHonoApp(options) {
5025
5651
  tenantID: ctxTenantID,
5026
5652
  draft: true
5027
5653
  });
5028
- if (isDraftEnabled) {
5029
- if (!isAlreadyPublished) {
5030
- await db.createVersion({
5654
+ if (collection.hooks?.afterChange) {
5655
+ for (const hook of collection.hooks.afterChange) {
5656
+ await hook({
5031
5657
  collection: slug,
5032
- documentId: id,
5658
+ doc,
5033
5659
  data: validated,
5034
- status: "draft",
5035
- createdBy: ctxUser?.id,
5036
- changeDescription: "Manual save",
5037
- tenantID: ctxTenantID
5660
+ originalDoc,
5661
+ req: hookReq,
5662
+ user: ctxUser,
5663
+ tenantID: ctxTenantID,
5664
+ operation: "update"
5038
5665
  });
5039
5666
  }
5040
- } else {
5041
- await db.deleteDraft({
5042
- collection: slug,
5043
- documentId: id,
5044
- tenantID: ctxTenantID
5045
- });
5046
5667
  }
5047
5668
  if (webhookService) {
5048
5669
  webhookService.trigger(getWebhookEvent(slug, "update"), {
@@ -5054,16 +5675,26 @@ function createHonoApp(options) {
5054
5675
  tenantId: ctxTenantID
5055
5676
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
5056
5677
  }
5057
- console.log(`[PATCH] Result doc updatedAt:`, doc?.updatedAt);
5678
+ auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "PATCH", c.req.raw);
5679
+ if (ctxUser) {
5680
+ sessionAuthAdapter?.createAuditLog({
5681
+ action: "document_update",
5682
+ userId: ctxUser.id,
5683
+ resource: slug,
5684
+ resourceId: id,
5685
+ success: true
5686
+ });
5687
+ }
5058
5688
  return c.json({ data: doc, message: isDraftEnabled ? "Draft saved" : "Updated successfully" });
5059
5689
  } catch (error) {
5060
5690
  if (error.name === "ZodError") {
5061
5691
  console.error(`[PATCH ${basePath}/:id] Validation failed:`, error.errors);
5062
5692
  return c.json(
5063
- { error: "Validation failed", details: error.errors },
5693
+ { error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
5064
5694
  400
5065
5695
  );
5066
5696
  }
5697
+ console.error(`[PATCH ${basePath}/:id] ERROR:`, error.message, `CAUSE:`, error.cause?.message || error.cause, `QUERY:`, error.query);
5067
5698
  return c.json({ error: error.message }, 500);
5068
5699
  }
5069
5700
  });
@@ -5087,23 +5718,50 @@ function createHonoApp(options) {
5087
5718
  if (!access.allowed) {
5088
5719
  return c.json({ error: access.error }, access.status || 403);
5089
5720
  }
5721
+ if (ctxTenantID) {
5722
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5723
+ }
5090
5724
  const id = c.req.param("id");
5091
- console.log(`[DELETE] Deleting ${slug}/${id}`);
5725
+ const hookReq = c.req.raw;
5092
5726
  const originalDoc = await db.findByID({
5093
5727
  collection: slug,
5094
5728
  id,
5095
- tenantID: ctxTenantID
5729
+ tenantID: ctxTenantID,
5730
+ draft: true
5096
5731
  });
5732
+ if (!originalDoc) {
5733
+ return c.json({ error: "Document not found" }, 404);
5734
+ }
5735
+ if (collection.hooks?.beforeDelete) {
5736
+ for (const hook of collection.hooks.beforeDelete) {
5737
+ await hook({
5738
+ collection: slug,
5739
+ doc: originalDoc,
5740
+ req: hookReq,
5741
+ user: ctxUser,
5742
+ tenantID: ctxTenantID,
5743
+ operation: "delete"
5744
+ });
5745
+ }
5746
+ }
5097
5747
  const doc = await db.delete({
5098
5748
  collection: slug,
5099
5749
  id,
5100
5750
  tenantID: ctxTenantID
5101
5751
  });
5102
- await db.deleteDraft({
5103
- collection: slug,
5104
- documentId: id,
5105
- tenantID: ctxTenantID
5106
- });
5752
+ if (collection.hooks?.afterDelete) {
5753
+ for (const hook of collection.hooks.afterDelete) {
5754
+ await hook({
5755
+ collection: slug,
5756
+ doc,
5757
+ originalDoc,
5758
+ req: hookReq,
5759
+ user: ctxUser,
5760
+ tenantID: ctxTenantID,
5761
+ operation: "delete"
5762
+ });
5763
+ }
5764
+ }
5107
5765
  if (webhookService) {
5108
5766
  webhookService.trigger(getWebhookEvent(slug, "delete"), {
5109
5767
  collection: slug,
@@ -5114,6 +5772,16 @@ function createHonoApp(options) {
5114
5772
  tenantId: ctxTenantID
5115
5773
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
5116
5774
  }
5775
+ auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "DELETE", c.req.raw);
5776
+ if (ctxUser) {
5777
+ sessionAuthAdapter?.createAuditLog({
5778
+ action: "document_delete",
5779
+ userId: ctxUser.id,
5780
+ resource: slug,
5781
+ resourceId: id,
5782
+ success: true
5783
+ });
5784
+ }
5117
5785
  return c.json({ data: doc, message: "Deleted successfully" });
5118
5786
  } catch (error) {
5119
5787
  console.error(`[DELETE] Error deleting ${slug}:`, error);
@@ -5122,7 +5790,6 @@ function createHonoApp(options) {
5122
5790
  });
5123
5791
  app.post(`${basePath}/:id/duplicate`, async (c) => {
5124
5792
  try {
5125
- console.log(`[Duplicate] Request for ${slug}`);
5126
5793
  const {
5127
5794
  user: ctxUser,
5128
5795
  tenantID: ctxTenantID,
@@ -5139,25 +5806,21 @@ function createHonoApp(options) {
5139
5806
  defaultCollectionAccess
5140
5807
  );
5141
5808
  if (!access.allowed) {
5142
- console.log("[Duplicate] Access denied:", access.error);
5143
5809
  return c.json({ error: access.error }, access.status || 403);
5144
5810
  }
5145
5811
  const id = c.req.param("id");
5146
- console.log(`[Duplicate] ID: ${id}`);
5147
5812
  const originalDoc = await db.findByID({
5148
5813
  collection: slug,
5149
5814
  id,
5150
- tenantID: ctxTenantID
5815
+ tenantID: ctxTenantID,
5816
+ draft: true
5151
5817
  });
5152
5818
  if (!originalDoc) {
5153
- console.log("[Duplicate] Document not found");
5154
5819
  return c.json({ error: "Document not found" }, 404);
5155
5820
  }
5156
- console.log(`[Duplicate] Original doc:`, originalDoc);
5157
5821
  const { id: _oldId, createdAt: _oldCreated, updatedAt: _oldUpdated, ...docData } = originalDoc;
5158
5822
  const timestamp = Date.now().toString(36);
5159
5823
  const newSlug = `${originalDoc.slug || "document"}-copy-${timestamp}`;
5160
- console.log(`[Duplicate] New slug: ${newSlug}`);
5161
5824
  const newDoc = await db.create({
5162
5825
  collection: slug,
5163
5826
  data: {
@@ -5167,7 +5830,6 @@ function createHonoApp(options) {
5167
5830
  },
5168
5831
  tenantID: ctxTenantID
5169
5832
  });
5170
- console.log(`[Duplicate] Created successfully:`, newDoc.id);
5171
5833
  return c.json({ data: newDoc, message: "Document duplicated successfully" });
5172
5834
  } catch (error) {
5173
5835
  console.error("[Duplicate] Error:", error);
@@ -5207,7 +5869,7 @@ function createHonoApp(options) {
5207
5869
  const doc = await db.update({
5208
5870
  collection: slug,
5209
5871
  id,
5210
- data: { ...version.data, _status: "draft", _has_draft: false },
5872
+ data: { ...version.data, status: "draft" },
5211
5873
  tenantID: ctxTenantID
5212
5874
  });
5213
5875
  return c.json({
@@ -5252,7 +5914,7 @@ function createHonoApp(options) {
5252
5914
  const doc = await db.update({
5253
5915
  collection: slug,
5254
5916
  id: c.req.param("id"),
5255
- data: { ...version.data, _status: "draft", _has_draft: false },
5917
+ data: { ...version.data, status: "draft" },
5256
5918
  tenantID: ctxTenantID
5257
5919
  });
5258
5920
  return c.json({
@@ -5285,6 +5947,9 @@ function createHonoApp(options) {
5285
5947
  if (!access.allowed) {
5286
5948
  return c.json({ error: access.error }, access.status || 403);
5287
5949
  }
5950
+ if (ctxTenantID) {
5951
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5952
+ }
5288
5953
  const id = c.req.param("id");
5289
5954
  const body = await c.req.json().catch(() => ({}));
5290
5955
  const baseUpdatedAt = readBaseUpdatedAt(body);
@@ -5300,9 +5965,9 @@ function createHonoApp(options) {
5300
5965
  if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
5301
5966
  return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
5302
5967
  }
5303
- let publishData = { _status: "published", _has_draft: false };
5968
+ let publishData = { status: "published" };
5304
5969
  let finalContent = originalDoc;
5305
- if (originalDoc._has_draft) {
5970
+ if (collection.versions?.drafts) {
5306
5971
  const versions = await db.findVersions({
5307
5972
  collection: slug,
5308
5973
  documentId: id,
@@ -5310,9 +5975,10 @@ function createHonoApp(options) {
5310
5975
  sort: "-createdAt",
5311
5976
  tenantID: ctxTenantID
5312
5977
  });
5313
- if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
5314
- finalContent = { ...originalDoc, ...versions.docs[0].data };
5315
- publishData = { ...versions.docs[0].data, ...publishData };
5978
+ if (versions.docs.length > 0) {
5979
+ const latestVersion = versions.docs[0];
5980
+ finalContent = { ...originalDoc, ...latestVersion.data };
5981
+ publishData = { ...latestVersion.data, ...publishData };
5316
5982
  }
5317
5983
  }
5318
5984
  const doc = await db.update({
@@ -5325,18 +5991,13 @@ function createHonoApp(options) {
5325
5991
  await db.createVersion({
5326
5992
  collection: slug,
5327
5993
  documentId: id,
5328
- data: { ...finalContent, _status: "published" },
5994
+ data: { ...finalContent, status: "published" },
5329
5995
  status: "published",
5330
5996
  createdBy: ctxUser?.id,
5331
5997
  changeDescription: "Published",
5332
5998
  tenantID: ctxTenantID
5333
5999
  });
5334
6000
  }
5335
- await db.deleteDraft({
5336
- collection: slug,
5337
- documentId: id,
5338
- tenantID: ctxTenantID
5339
- });
5340
6001
  if (webhookService) {
5341
6002
  webhookService.trigger(getWebhookEvent(slug, "update"), {
5342
6003
  collection: slug,
@@ -5384,7 +6045,7 @@ function createHonoApp(options) {
5384
6045
  const doc = await db.update({
5385
6046
  collection: slug,
5386
6047
  id,
5387
- data: { _status: "draft" },
6048
+ data: { status: "draft" },
5388
6049
  tenantID: ctxTenantID
5389
6050
  });
5390
6051
  if (webhookService) {
@@ -5419,13 +6080,31 @@ function createHonoApp(options) {
5419
6080
  if (!access.allowed) {
5420
6081
  return c.json({ error: access.error }, access.status || 403);
5421
6082
  }
5422
- const isDraftRequest = c.req.query("draft") === "true" && !!ctxUser;
5423
- const doc = await db.findOne({
6083
+ const isDraftRequest = !!ctxUser;
6084
+ let doc = await db.findOne({
5424
6085
  collection: `_globals_${slug}`,
5425
6086
  where: {},
5426
6087
  tenantID: ctxTenantID,
5427
6088
  draft: isDraftRequest
5428
6089
  });
6090
+ if (slug === "system") {
6091
+ const newSecret = crypto__default.default.randomBytes(32).toString("hex");
6092
+ if (!doc) {
6093
+ doc = await db.create({
6094
+ collection: `_globals_${slug}`,
6095
+ data: { id: slug, appSecret: newSecret },
6096
+ tenantID: ctxTenantID
6097
+ });
6098
+ } else if (!doc.appSecret) {
6099
+ await db.update({
6100
+ collection: `_globals_${slug}`,
6101
+ id: slug,
6102
+ data: { appSecret: newSecret },
6103
+ tenantID: ctxTenantID
6104
+ });
6105
+ doc.appSecret = newSecret;
6106
+ }
6107
+ }
5429
6108
  return c.json({ data: doc || {} });
5430
6109
  } catch (error) {
5431
6110
  return c.json({ error: error.message }, 500);
@@ -5445,18 +6124,20 @@ function createHonoApp(options) {
5445
6124
  if (!access.allowed) {
5446
6125
  return c.json({ error: access.error }, access.status || 403);
5447
6126
  }
5448
- const body = await c.req.json();
6127
+ const body = omitRevisionFields(await c.req.json());
5449
6128
  const cleaned = Object.fromEntries(
5450
6129
  Object.entries(body).filter(([_, v]) => v !== null && v !== "null" && v !== void 0)
5451
6130
  );
5452
- const schema = registry.getZodSchema(slug);
6131
+ normalizeEmptyStrings(cleaned, globalConfig.fields);
6132
+ convertRichtextFields(globalConfig.fields, cleaned);
6133
+ const schema = registry.getUpdateZodSchema(slug);
5453
6134
  let validated;
5454
6135
  try {
5455
6136
  validated = schema.parse(cleaned);
5456
6137
  } catch (zodErr) {
5457
- return c.json({ error: "Validation failed", details: zodErr.errors }, 400);
6138
+ return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
5458
6139
  }
5459
- const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "_status", "_has_draft"]);
6140
+ const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "status", "baseUpdatedAt", "_baseUpdatedAt"]);
5460
6141
  const userData = Object.fromEntries(
5461
6142
  Object.entries(validated).filter(([k]) => !SYSTEM_FIELDS.has(k))
5462
6143
  );
@@ -5467,49 +6148,68 @@ function createHonoApp(options) {
5467
6148
  tenantID: ctxTenantID,
5468
6149
  draft: true
5469
6150
  });
6151
+ const isDraft = c.req.header("X-Draft") === "true";
5470
6152
  const isDraftEnabled = globalConfig.versions?.drafts === true;
5471
- const isAlreadyPublished = originalDoc?._status === "published";
6153
+ const isAutosave = c.req.query("autosave") === "true";
5472
6154
  let doc;
5473
- if (isDraftEnabled && isAlreadyPublished) {
6155
+ if (isDraftEnabled && isDraft) {
5474
6156
  await db.createVersion({
5475
6157
  collection: collectionSlug,
5476
6158
  documentId: slug,
5477
6159
  data: userData,
5478
6160
  status: "draft",
6161
+ autosave: isAutosave,
5479
6162
  createdBy: ctxUser?.id,
5480
- changeDescription: "Manual save (Draft)",
5481
- tenantID: ctxTenantID
5482
- });
5483
- doc = await db.update({
5484
- collection: collectionSlug,
5485
- id: slug,
5486
- data: { _has_draft: true },
6163
+ changeDescription: isAutosave ? "Autosave" : "Manual save",
5487
6164
  tenantID: ctxTenantID
5488
6165
  });
5489
- } else {
5490
- const saveData = isDraftEnabled ? { ...userData, _status: "draft", _has_draft: false } : { ...userData, _status: "published", _has_draft: false };
6166
+ if (!originalDoc) {
6167
+ doc = await db.create({
6168
+ collection: collectionSlug,
6169
+ data: { ...userData, id: slug, status: "draft" },
6170
+ tenantID: ctxTenantID
6171
+ });
6172
+ } else {
6173
+ doc = originalDoc;
6174
+ }
6175
+ } else if (isDraftEnabled && !isDraft) {
6176
+ const publishStatus = "published";
5491
6177
  if (originalDoc) {
5492
6178
  doc = await db.update({
5493
6179
  collection: collectionSlug,
5494
6180
  id: slug,
5495
- data: saveData,
6181
+ data: { ...userData, status: publishStatus },
5496
6182
  tenantID: ctxTenantID
5497
6183
  });
5498
6184
  } else {
5499
6185
  doc = await db.create({
5500
6186
  collection: collectionSlug,
5501
- data: { ...saveData, id: slug },
6187
+ data: { ...userData, id: slug, status: publishStatus },
5502
6188
  tenantID: ctxTenantID
5503
6189
  });
5504
6190
  }
5505
- if (isDraftEnabled) {
5506
- await db.createVersion({
6191
+ await db.createVersion({
6192
+ collection: collectionSlug,
6193
+ documentId: slug,
6194
+ data: userData,
6195
+ status: publishStatus,
6196
+ autosave: false,
6197
+ createdBy: ctxUser?.id,
6198
+ changeDescription: "Published",
6199
+ tenantID: ctxTenantID
6200
+ });
6201
+ } else {
6202
+ if (originalDoc) {
6203
+ doc = await db.update({
5507
6204
  collection: collectionSlug,
5508
- documentId: slug,
6205
+ id: slug,
5509
6206
  data: userData,
5510
- status: "draft",
5511
- createdBy: ctxUser?.id,
5512
- changeDescription: "Manual save",
6207
+ tenantID: ctxTenantID
6208
+ });
6209
+ } else {
6210
+ doc = await db.create({
6211
+ collection: collectionSlug,
6212
+ data: { ...userData, id: slug },
5513
6213
  tenantID: ctxTenantID
5514
6214
  });
5515
6215
  }
@@ -5518,6 +6218,22 @@ function createHonoApp(options) {
5518
6218
  mediaService = null;
5519
6219
  mediaServiceInitError = null;
5520
6220
  }
6221
+ if (slug === "email-settings") {
6222
+ const newEmailTransport = await chunkY7AQK4R4_cjs.EmailTransport.fromConfig(db);
6223
+ authRoutes.email = newEmailTransport || void 0;
6224
+ }
6225
+ if (slug === "system") {
6226
+ await loadSecrets();
6227
+ }
6228
+ if (ctxUser) {
6229
+ sessionAuthAdapter?.createAuditLog({
6230
+ action: "settings_change",
6231
+ userId: ctxUser.id,
6232
+ resource: `global:${slug}`,
6233
+ resourceId: slug,
6234
+ success: true
6235
+ });
6236
+ }
5521
6237
  return c.json({ data: doc, message: "Updated successfully" });
5522
6238
  } catch (error) {
5523
6239
  console.error(`[API] Save global "${slug}" failed:`, error);
@@ -5542,9 +6258,9 @@ function createHonoApp(options) {
5542
6258
  draft: true
5543
6259
  });
5544
6260
  if (!originalDoc) return c.json({ error: "Global not found" }, 404);
5545
- let publishData = { _status: "published", _has_draft: false };
6261
+ let publishData = { status: "published" };
5546
6262
  let finalContent = originalDoc;
5547
- if (originalDoc._has_draft) {
6263
+ if (globalConfig.versions?.drafts) {
5548
6264
  const versions = await db.findVersions({
5549
6265
  collection: collectionSlug,
5550
6266
  documentId: slug,
@@ -5552,7 +6268,7 @@ function createHonoApp(options) {
5552
6268
  sort: "-createdAt",
5553
6269
  tenantID: ctxTenantID
5554
6270
  });
5555
- if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
6271
+ if (versions.docs.length > 0) {
5556
6272
  finalContent = { ...originalDoc, ...versions.docs[0].data };
5557
6273
  publishData = { ...versions.docs[0].data, ...publishData };
5558
6274
  }
@@ -5567,7 +6283,7 @@ function createHonoApp(options) {
5567
6283
  await db.createVersion({
5568
6284
  collection: collectionSlug,
5569
6285
  documentId: slug,
5570
- data: { ...finalContent, _status: "published" },
6286
+ data: { ...finalContent, status: "published" },
5571
6287
  status: "published",
5572
6288
  createdBy: ctxUser?.id,
5573
6289
  changeDescription: "Published",
@@ -5587,7 +6303,7 @@ function createHonoApp(options) {
5587
6303
  const doc = await db.update({
5588
6304
  collection: `_globals_${slug}`,
5589
6305
  id: slug,
5590
- data: { _status: "draft", _has_draft: false },
6306
+ data: { status: "draft" },
5591
6307
  tenantID: ctxTenantID
5592
6308
  });
5593
6309
  return c.json({ data: doc, message: "Unpublished successfully" });
@@ -5647,9 +6363,13 @@ function createHonoApp(options) {
5647
6363
  const doc = await db.update({
5648
6364
  collection: collectionSlug,
5649
6365
  id: slug,
5650
- data: { ...version.data, _status: "draft", _has_draft: false },
6366
+ data: { ...version.data, status: "draft" },
5651
6367
  tenantID: ctxTenantID
5652
6368
  });
6369
+ return c.json({
6370
+ data: doc,
6371
+ message: "Version restored successfully"
6372
+ });
5653
6373
  return c.json({ data: doc, message: "Restored successfully" });
5654
6374
  } catch (error) {
5655
6375
  return c.json({ error: error.message }, 500);
@@ -5693,7 +6413,7 @@ function createHonoApp(options) {
5693
6413
  mailgun: body.mailgun,
5694
6414
  ses: body.ses
5695
6415
  };
5696
- const transport = new chunkIA6AU5PI_cjs.EmailTransport(transportConfig);
6416
+ const transport = new chunkY7AQK4R4_cjs.EmailTransport(transportConfig);
5697
6417
  const recipient = body.testEmail || body.testEmailSection && body.testEmailSection.testEmail;
5698
6418
  if (!recipient) {
5699
6419
  return c.json({ error: "No test recipient email provided" }, 400);
@@ -5757,15 +6477,19 @@ exports.InMemoryAuditLogger = InMemoryAuditLogger;
5757
6477
  exports.InMemoryRateLimiter = InMemoryRateLimiter;
5758
6478
  exports.MediaService = MediaService;
5759
6479
  exports.createAuditContext = createAuditContext2;
6480
+ exports.createCloudinaryStorage = createCloudinaryStorage;
6481
+ exports.createFtpStorage = createFtpStorage;
5760
6482
  exports.createHonoApp = createHonoApp;
5761
6483
  exports.createLocalStorage = createLocalStorage;
5762
6484
  exports.createRESTAPI = createRESTAPI;
6485
+ exports.createS3Storage = createS3Storage;
5763
6486
  exports.getAppSecret = getAppSecret;
6487
+ exports.getDefaultRegistry = getDefaultRegistry;
5764
6488
  exports.getEncryptionKey = getEncryptionKey;
5765
6489
  exports.getSessionConfig = getSessionConfig;
5766
6490
  exports.init_secret = init_secret;
5767
6491
  exports.loadSecrets = loadSecrets;
5768
6492
  exports.resolveProvider = resolveProvider;
5769
6493
  exports.setDbAdapter = setDbAdapter;
5770
- //# sourceMappingURL=chunk-5KVM3WEY.cjs.map
5771
- //# sourceMappingURL=chunk-5KVM3WEY.cjs.map
6494
+ //# sourceMappingURL=chunk-CNKT4PME.cjs.map
6495
+ //# sourceMappingURL=chunk-CNKT4PME.cjs.map