@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,10 +1,13 @@
1
- import { createKyroServer } from './chunk-3AJE4SEG.js';
2
- import { createHonoApp } from './chunk-HXRD4B37.js';
3
- import { createWebhookService, API_KEY_COLLECTION, WEBHOOK_COLLECTION, WEBHOOK_DELIVERY_COLLECTION } from './chunk-QXIQWPAP.js';
4
- import { buildGraphQLSchema } from './chunk-REK7AYOC.js';
5
- import { KyroPubSub, createWSServer } from './chunk-3TPQ2BU6.js';
6
- import { AbstractBaseAdapter, applyRLS, DEFAULT_RLS_CONFIG, canAccessDocument } from './chunk-3ZFYL34R.js';
1
+ import { createStorageSettingsGlobal } from './chunk-GAOXD3XT.js';
2
+ import { createContext, createKyroServer } from './chunk-OHC6UHFY.js';
3
+ import { buildGraphQLSchema } from './chunk-L5UKKZQN.js';
4
+ import { getDefaultRegistry, createHonoApp, createS3Storage, createCloudinaryStorage, createFtpStorage } from './chunk-IPTZM3VE.js';
5
+ import { createWebhookService, WEBHOOK_COLLECTION, WEBHOOK_DELIVERY_COLLECTION } from './chunk-3UK5XBVJ.js';
6
+ import { API_KEY_COLLECTION, extractApiKeyFromRequest, validateApiKey, createApiKeyContext } from './chunk-CJONKRHJ.js';
7
+ import { KyroPubSub, createWSServer } from './chunk-BYBMTIMT.js';
8
+ import { AbstractBaseAdapter, applyRLS, DEFAULT_RLS_CONFIG, canAccessDocument } from './chunk-WXVB364T.js';
7
9
  import { z } from 'zod';
10
+ import { parse, execute } from 'graphql';
8
11
  import { createRequire } from 'module';
9
12
  import { randomBytes } from 'crypto';
10
13
 
@@ -174,9 +177,9 @@ function validateFields(fields, context) {
174
177
  break;
175
178
  case "select":
176
179
  case "radio":
177
- if (!field.options || field.options.length === 0) {
180
+ if ((!field.options || field.options.length === 0) && !field.dynamicOptions) {
178
181
  errors.push(`${context}: ${field.type} field "${fieldName}" has no options defined`);
179
- } else {
182
+ } else if (field.options) {
180
183
  const values = field.options.map((o) => o.value);
181
184
  const uniqueValues = new Set(values);
182
185
  if (values.length !== uniqueValues.size) {
@@ -265,7 +268,7 @@ function validateRelationships(fields, collections) {
265
268
  const targets = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo];
266
269
  for (const target of targets) {
267
270
  if (!collectionSlugs.has(target)) {
268
- errors.push(`Relationship field "${field.name}" references unknown collection "${target}"`);
271
+ console.warn(`[Kyro Config Warning]: Relationship field "${field.name}" references unknown collection "${target}". Select options will not be available until this collection is registered.`);
269
272
  }
270
273
  }
271
274
  }
@@ -365,7 +368,7 @@ function textToZod(field) {
365
368
  return schema;
366
369
  }
367
370
  function numberToZod(field) {
368
- let schema = field.integer ? z.number().int() : z.number();
371
+ let schema = field.integer ? z.coerce.number().int() : z.coerce.number();
369
372
  if (field.min !== void 0) schema = schema.min(field.min);
370
373
  if (field.max !== void 0) schema = schema.max(field.max);
371
374
  if (field.step) {
@@ -424,12 +427,16 @@ function textareaToZod(field) {
424
427
  return schema;
425
428
  }
426
429
  function selectToZod(field) {
427
- const values = field.options.map((opt) => opt.value);
428
430
  let schema;
429
- if (field.hasMany) {
430
- schema = z.array(z.enum(values));
431
+ if (field.options && field.options.length > 0) {
432
+ const values = field.options.map((opt) => opt.value);
433
+ if (field.hasMany) {
434
+ schema = z.array(z.enum(values));
435
+ } else {
436
+ schema = z.enum(values);
437
+ }
431
438
  } else {
432
- schema = z.enum(values);
439
+ schema = field.hasMany ? z.array(z.string()) : z.string();
433
440
  }
434
441
  if (!field.required) schema = schema.optional().nullable();
435
442
  if (field.validate) schema = addCustomValidation(schema, field.validate);
@@ -459,10 +466,7 @@ function colorToZod(field) {
459
466
  return schema;
460
467
  }
461
468
  function richTextToZod(field) {
462
- let schema = z.union([
463
- z.array(z.record(z.any())),
464
- z.string()
465
- ]);
469
+ let schema = z.array(z.record(z.any()));
466
470
  if (!field.required) schema = schema.optional().nullable();
467
471
  if (field.validate) schema = addCustomValidation(schema, field.validate);
468
472
  return schema;
@@ -573,9 +577,9 @@ function blocksToZod(field) {
573
577
  const unknownSchema = z.object({
574
578
  blockType: z.string()
575
579
  }).catchall(z.any());
576
- schema = z.array(z.union([knownSchema, unknownSchema]));
580
+ schema = z.array(z.union([knownSchema, unknownSchema, z.record(z.any())]));
577
581
  } else {
578
- schema = z.array(z.object({ blockType: z.string() }).catchall(z.any()));
582
+ schema = z.array(z.union([z.object({ blockType: z.string() }).catchall(z.any()), z.record(z.any())]));
579
583
  }
580
584
  if (field.minRows) schema = schema.min(field.minRows);
581
585
  if (field.maxRows) schema = schema.max(field.maxRows);
@@ -643,6 +647,30 @@ function buildNestedShape(fields) {
643
647
  }
644
648
  return shape;
645
649
  }
650
+ function buildUpdateNestedShape(fields) {
651
+ const shape = {};
652
+ for (const field of fields) {
653
+ if (!field.name) continue;
654
+ if (field.type === "tabs" && "tabs" in field) {
655
+ const tabShape = {};
656
+ for (const tab of field.tabs) {
657
+ const nestedShape = buildUpdateNestedShape(tab.fields);
658
+ Object.assign(tabShape, nestedShape);
659
+ }
660
+ shape[field.name] = z.object(tabShape).passthrough().optional().nullable();
661
+ } else if (field.type === "row" && "fields" in field) {
662
+ const rowShape = buildUpdateNestedShape(field.fields);
663
+ Object.assign(shape, rowShape);
664
+ } else if (field.type === "collapsible" && "fields" in field) {
665
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).passthrough().optional().nullable();
666
+ } else if (field.type === "group" && "fields" in field) {
667
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).passthrough().optional().nullable();
668
+ } else {
669
+ shape[field.name] = fieldToZod(field).optional().nullable();
670
+ }
671
+ }
672
+ return shape;
673
+ }
646
674
  function collectionToZod(collection) {
647
675
  const shape = buildNestedShape(collection.fields);
648
676
  if (collection.timestamps) {
@@ -682,7 +710,9 @@ function collectionToUpdateZod(collection) {
682
710
  }
683
711
  Object.assign(shape, rowShape);
684
712
  } else if (field.type === "collapsible" && "fields" in field) {
685
- shape[field.name] = z.object(buildNestedShape(field.fields)).optional().nullable();
713
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).optional().nullable();
714
+ } else if (field.type === "group" && "fields" in field) {
715
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).optional().nullable();
686
716
  } else {
687
717
  shape[field.name] = fieldToZod(field).optional().nullable();
688
718
  }
@@ -718,9 +748,34 @@ function globalToZod(global) {
718
748
  shape["id"] = z.string().optional();
719
749
  return z.object(shape).passthrough();
720
750
  }
751
+ function globalToUpdateZod(global) {
752
+ const shape = {};
753
+ for (const field of global.fields) {
754
+ if (!field.name) continue;
755
+ if (field.type === "tabs" && "tabs" in field) {
756
+ const tabShape = {};
757
+ for (const tab of field.tabs) {
758
+ for (const tabField of tab.fields) {
759
+ if (tabField.name) {
760
+ tabShape[tabField.name] = fieldToZod(tabField).optional().nullable();
761
+ }
762
+ }
763
+ }
764
+ shape[field.name] = z.object(tabShape).optional().nullable();
765
+ } else if (field.type === "collapsible" && "fields" in field) {
766
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).optional().nullable();
767
+ } else if (field.type === "group" && "fields" in field) {
768
+ shape[field.name] = z.object(buildUpdateNestedShape(field.fields)).optional().nullable();
769
+ } else {
770
+ shape[field.name] = fieldToZod(field).optional().nullable();
771
+ }
772
+ }
773
+ return z.object(shape).passthrough();
774
+ }
721
775
 
722
776
  // src/registry/index.ts
723
777
  var Registry = class {
778
+ storageProviders = getDefaultRegistry();
724
779
  collections = /* @__PURE__ */ new Map();
725
780
  globals = /* @__PURE__ */ new Map();
726
781
  plugins = [];
@@ -788,6 +843,19 @@ var Registry = class {
788
843
  if (this.initialized) {
789
844
  throw new Error("Cannot add globals after Registry has been initialized");
790
845
  }
846
+ this._addGlobalUnsafe(config);
847
+ }
848
+ /**
849
+ * Add a global after the registry is already initialized.
850
+ * Only for internal use (e.g. storage settings form built at startup).
851
+ */
852
+ addGlobalPostInit(config) {
853
+ if (this.globals.has(config.slug)) {
854
+ this.globals.delete(config.slug);
855
+ }
856
+ this._addGlobalUnsafe(config);
857
+ }
858
+ _addGlobalUnsafe(config) {
791
859
  if (this.globals.has(config.slug)) {
792
860
  console.warn(
793
861
  `[Registry] Duplicate global slug "${config.slug}" \u2014 skipping`
@@ -887,7 +955,13 @@ var Registry = class {
887
955
  this.schemaCache.set(cacheKey, schema);
888
956
  return schema;
889
957
  }
890
- throw new Error(`No collection found with slug "${slug}"`);
958
+ const global = this.globals.get(slug);
959
+ if (global) {
960
+ const schema = globalToUpdateZod(global);
961
+ this.schemaCache.set(`global:${slug}:update`, schema);
962
+ return schema;
963
+ }
964
+ throw new Error(`No collection or global found with slug "${slug}"`);
891
965
  }
892
966
  getWhereZodSchema(slug) {
893
967
  const cacheKey = `${slug}:where`;
@@ -942,6 +1016,17 @@ var Registry = class {
942
1016
  admin: { readOnly: true, hidden: true }
943
1017
  });
944
1018
  }
1019
+ if (config.versions?.drafts && !fields.some((f) => f.name === "status")) {
1020
+ fields.push({
1021
+ name: "status",
1022
+ type: "select",
1023
+ options: [
1024
+ { value: "draft", label: "Draft" },
1025
+ { value: "published", label: "Published" }
1026
+ ],
1027
+ admin: { readOnly: true, hidden: true }
1028
+ });
1029
+ }
945
1030
  if (config.auth && !fields.some((f) => f.name === "password")) {
946
1031
  fields.push({
947
1032
  name: "password",
@@ -1086,6 +1171,296 @@ function createRegistry() {
1086
1171
  instance = new Registry();
1087
1172
  return instance;
1088
1173
  }
1174
+
1175
+ // src/plugins/storage-s3.ts
1176
+ var s3Variants = {
1177
+ aws: {
1178
+ type: "aws",
1179
+ configKey: "s3",
1180
+ displayName: "S3 Compatible (AWS, Backblaze, Wasabi, etc.)",
1181
+ configFields: [
1182
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1183
+ { name: "region", type: "text", label: "Region", defaultValue: "us-east-1", admin: { placeholder: "us-east-1" } },
1184
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1185
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1186
+ { name: "endpoint", type: "text", label: "Endpoint URL", admin: { placeholder: "https://s3.custom.com" } },
1187
+ { name: "cdnUrl", type: "text", label: "CDN URL", admin: { placeholder: "https://cdn.example.com" } },
1188
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads" } }
1189
+ ]
1190
+ },
1191
+ r2: {
1192
+ type: "r2",
1193
+ configKey: "r2",
1194
+ displayName: "Cloudflare R2",
1195
+ configFields: [
1196
+ { name: "accountId", type: "text", label: "Account ID", required: true, admin: { placeholder: "Your Cloudflare Account ID" } },
1197
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1198
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1199
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1200
+ {
1201
+ name: "publicDevUrl",
1202
+ type: "text",
1203
+ label: "Public Dev URL ID",
1204
+ admin: {
1205
+ placeholder: "pub-xxxxxxxxxxxxxxxx",
1206
+ description: "Enter ONLY the ID (e.g., pub-b8d8c4cc8bcf4d868ddd95efc1b305aa). Do NOT include https:// or the full URL. Found in R2 Dashboard \u2192 Public Dev URL."
1207
+ }
1208
+ },
1209
+ { name: "cdnUrl", type: "text", label: "Custom CDN URL", admin: { placeholder: "https://assets.example.com (optional)" } },
1210
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads (optional)", description: "Optional prefix for all object keys. Do not use '/' as prefix." } }
1211
+ ]
1212
+ },
1213
+ gcs: {
1214
+ type: "gcs",
1215
+ configKey: "gcs",
1216
+ displayName: "Google Cloud Storage",
1217
+ configFields: [
1218
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1219
+ { name: "projectId", type: "text", label: "Project ID" },
1220
+ { name: "clientEmail", type: "text", label: "Client Email" },
1221
+ { name: "privateKey", type: "password", label: "Private Key" },
1222
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1223
+ { name: "prefix", type: "text", label: "Path Prefix" }
1224
+ ]
1225
+ },
1226
+ digitalocean: {
1227
+ type: "digitalocean",
1228
+ configKey: "digitalocean",
1229
+ displayName: "DigitalOcean Spaces",
1230
+ configFields: [
1231
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1232
+ { name: "region", type: "text", label: "Region", defaultValue: "nyc3" },
1233
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1234
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1235
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1236
+ { name: "prefix", type: "text", label: "Path Prefix" }
1237
+ ]
1238
+ },
1239
+ backblaze: {
1240
+ type: "backblaze",
1241
+ configKey: "backblaze",
1242
+ displayName: "Backblaze B2",
1243
+ configFields: [
1244
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1245
+ { name: "accountId", type: "text", label: "Account ID" },
1246
+ { name: "applicationKeyId", type: "text", label: "Application Key ID", required: true },
1247
+ { name: "applicationKey", type: "password", label: "Application Key", required: true },
1248
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1249
+ { name: "prefix", type: "text", label: "Path Prefix" }
1250
+ ]
1251
+ },
1252
+ wasabi: {
1253
+ type: "wasabi",
1254
+ configKey: "wasabi",
1255
+ displayName: "Wasabi",
1256
+ configFields: [
1257
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1258
+ { name: "region", type: "text", label: "Region", defaultValue: "us-east-1" },
1259
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1260
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1261
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1262
+ { name: "prefix", type: "text", label: "Path Prefix" }
1263
+ ]
1264
+ }
1265
+ };
1266
+ function getEndpoint(type, config) {
1267
+ switch (type) {
1268
+ case "r2":
1269
+ return config?.endpoint || `https://${config?.accountId || ""}.r2.cloudflarestorage.com`;
1270
+ case "digitalocean":
1271
+ return config?.endpoint || `https://${config?.region || "nyc3"}.digitaloceanspaces.com`;
1272
+ case "backblaze":
1273
+ return config?.endpoint || `https://s3.backblazeb2.com`;
1274
+ case "wasabi":
1275
+ return config?.endpoint || `https://s3.${config?.region || "us-east-1"}.wasabisys.com`;
1276
+ default:
1277
+ return config?.endpoint;
1278
+ }
1279
+ }
1280
+ function buildS3Config(type, c) {
1281
+ return {
1282
+ provider: type,
1283
+ bucket: c?.bucket || "",
1284
+ region: c?.region || "us-east-1",
1285
+ accessKeyId: c?.accessKeyId || c?.clientEmail || c?.applicationKeyId || "",
1286
+ secretAccessKey: c?.secretAccessKey || c?.privateKey || c?.applicationKey || "",
1287
+ endpoint: getEndpoint(type, c),
1288
+ cdnUrl: c?.cdnUrl,
1289
+ prefix: c?.prefix,
1290
+ accountId: c?.accountId,
1291
+ publicDevUrl: c?.publicDevUrl
1292
+ };
1293
+ }
1294
+ function buildS3ConfigFromStorageConfig(type, def, sc) {
1295
+ const c = sc[def.configKey] || {};
1296
+ return buildS3Config(type, c);
1297
+ }
1298
+ function buildS3ConfigFromRaw(type, def, raw) {
1299
+ const c = raw?.[def.configKey] || raw;
1300
+ return buildS3Config(type, c);
1301
+ }
1302
+ var s3StoragePlugin = {
1303
+ name: "@kyro-cms/storage-s3",
1304
+ version: "1.0.0",
1305
+ description: "S3-compatible storage (AWS R2 GCS DigitalOcean Backblaze Wasabi)",
1306
+ init: (kyro) => {
1307
+ const registry = kyro.storageProviders;
1308
+ if (!registry) return;
1309
+ const pluginName = "@kyro-cms/storage-s3";
1310
+ for (const v of Object.values(s3Variants)) {
1311
+ registry.register({
1312
+ type: v.type,
1313
+ displayName: v.displayName,
1314
+ pluginName,
1315
+ configKey: v.configKey,
1316
+ configFields: v.configFields,
1317
+ extractConfig: (sc) => buildS3ConfigFromStorageConfig(v.type, v, sc),
1318
+ extractRawConfig: (raw) => buildS3ConfigFromRaw(v.type, v, raw),
1319
+ factory: (c) => createS3Storage(c)
1320
+ });
1321
+ }
1322
+ }
1323
+ };
1324
+
1325
+ // src/plugins/storage-cloudinary.ts
1326
+ var cloudinaryStoragePlugin = {
1327
+ name: "@kyro-cms/storage-cloudinary",
1328
+ version: "1.0.0",
1329
+ description: "Cloudinary image and video storage",
1330
+ init: (kyro) => {
1331
+ const registry = kyro.storageProviders;
1332
+ if (!registry) return;
1333
+ registry.register({
1334
+ type: "cloudinary",
1335
+ displayName: "Cloudinary",
1336
+ pluginName: "@kyro-cms/storage-cloudinary",
1337
+ configKey: "cloudinary",
1338
+ configFields: [
1339
+ { name: "cloudName", type: "text", label: "Cloud Name", required: true },
1340
+ { name: "apiKey", type: "text", label: "API Key", required: true },
1341
+ { name: "apiSecret", type: "password", label: "API Secret", required: true },
1342
+ { name: "folder", type: "text", label: "Folder", admin: { placeholder: "Optional folder path" } },
1343
+ {
1344
+ name: "uploadPreset",
1345
+ type: "text",
1346
+ label: "Upload Preset (optional)",
1347
+ admin: { placeholder: "Leave empty for signed uploads", description: "If not set, uploads will be signed with API Secret" }
1348
+ }
1349
+ ],
1350
+ extractConfig: (sc) => ({
1351
+ cloudName: sc.cloudinary?.cloudName || "",
1352
+ apiKey: sc.cloudinary?.apiKey || "",
1353
+ apiSecret: sc.cloudinary?.apiSecret || "",
1354
+ folder: sc.cloudinary?.folder,
1355
+ uploadPreset: sc.cloudinary?.uploadPreset
1356
+ }),
1357
+ extractRawConfig: (c) => {
1358
+ const cl = c?.cloudinary || c;
1359
+ return {
1360
+ cloudName: cl?.cloudName || "",
1361
+ apiKey: cl?.apiKey || "",
1362
+ apiSecret: cl?.apiSecret || "",
1363
+ folder: cl?.folder,
1364
+ uploadPreset: cl?.uploadPreset
1365
+ };
1366
+ },
1367
+ factory: (c) => createCloudinaryStorage(c)
1368
+ });
1369
+ }
1370
+ };
1371
+
1372
+ // src/plugins/storage-ftp.ts
1373
+ var ftpStoragePlugin = {
1374
+ name: "@kyro-cms/storage-ftp",
1375
+ version: "1.0.0",
1376
+ description: "FTP/SFTP storage provider",
1377
+ init: (kyro) => {
1378
+ const registry = kyro.storageProviders;
1379
+ if (!registry) return;
1380
+ registry.register({
1381
+ type: "ftp",
1382
+ displayName: "FTP",
1383
+ pluginName: "@kyro-cms/storage-ftp",
1384
+ configKey: "ftp",
1385
+ configFields: [
1386
+ { name: "host", type: "text", label: "Host", required: true, admin: { placeholder: "ftp.example.com" } },
1387
+ { name: "port", type: "number", label: "Port", defaultValue: 21, admin: { placeholder: "21 for FTP" } },
1388
+ { name: "user", type: "text", label: "Username", required: true },
1389
+ { name: "password", type: "password", label: "Password", required: true },
1390
+ { name: "secure", type: "checkbox", label: "Use TLS/SSL", defaultValue: false, admin: { description: "Enable TLS/SSL for secure connections (FTP only)" } },
1391
+ { name: "baseUrl", type: "text", label: "Base URL", required: true, admin: { placeholder: "https://files.example.com" } },
1392
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads" } }
1393
+ ],
1394
+ extractConfig: (sc) => ({
1395
+ host: sc.ftp?.host || "",
1396
+ port: sc.ftp?.port || 21,
1397
+ user: sc.ftp?.user || "",
1398
+ password: sc.ftp?.password || "",
1399
+ secure: sc.ftp?.secure || false,
1400
+ baseUrl: sc.ftp?.baseUrl || "",
1401
+ prefix: sc.ftp?.prefix,
1402
+ type: "ftp"
1403
+ }),
1404
+ extractRawConfig: (c) => {
1405
+ const ftp = c?.ftp || c;
1406
+ return {
1407
+ host: ftp?.host || "",
1408
+ port: ftp?.port || 21,
1409
+ user: ftp?.user || "",
1410
+ password: ftp?.password || "",
1411
+ secure: ftp?.secure || false,
1412
+ baseUrl: ftp?.baseUrl || "",
1413
+ prefix: ftp?.prefix,
1414
+ type: "ftp"
1415
+ };
1416
+ },
1417
+ factory: (c) => createFtpStorage(c)
1418
+ });
1419
+ }
1420
+ };
1421
+ var builtinStoragePlugins = [
1422
+ s3StoragePlugin,
1423
+ cloudinaryStoragePlugin,
1424
+ ftpStoragePlugin
1425
+ ];
1426
+ function updateFieldByPath(fields, path, updates) {
1427
+ const parts = path.split(".");
1428
+ if (parts.length === 0) return false;
1429
+ const currentPart = parts[0];
1430
+ const remainingPath = parts.slice(1).join(".");
1431
+ for (const field of fields) {
1432
+ if (field.name === currentPart) {
1433
+ if (remainingPath) {
1434
+ if (field.fields && Array.isArray(field.fields)) {
1435
+ return updateFieldByPath(field.fields, remainingPath, updates);
1436
+ }
1437
+ if (field.type === "array" && field.fields && Array.isArray(field.fields)) {
1438
+ return updateFieldByPath(field.fields, remainingPath, updates);
1439
+ }
1440
+ return false;
1441
+ } else {
1442
+ Object.assign(field, updates);
1443
+ return true;
1444
+ }
1445
+ }
1446
+ }
1447
+ return false;
1448
+ }
1449
+ function applyCollectionOverrides(collections, overrides) {
1450
+ if (!overrides) return;
1451
+ for (const col of collections) {
1452
+ const override = overrides[col.slug];
1453
+ if (override) {
1454
+ const { fields: fieldOverrides, ...adminOverrides } = override;
1455
+ col.admin = { ...col.admin, ...adminOverrides };
1456
+ if (fieldOverrides && col.fields && Array.isArray(col.fields)) {
1457
+ for (const [fieldPath, fieldUpdates] of Object.entries(fieldOverrides)) {
1458
+ updateFieldByPath(col.fields, fieldPath, fieldUpdates);
1459
+ }
1460
+ }
1461
+ }
1462
+ }
1463
+ }
1089
1464
  var Kyro = class {
1090
1465
  registry;
1091
1466
  db;
@@ -1100,12 +1475,18 @@ var Kyro = class {
1100
1475
  this.db = config.adapter;
1101
1476
  this.pubsub = new KyroPubSub(this.registry);
1102
1477
  this.webhookService = createWebhookService(this.db);
1478
+ if (config.collections && config.admin?.collectionOverrides) {
1479
+ applyCollectionOverrides(config.collections, config.admin.collectionOverrides);
1480
+ }
1103
1481
  if (config.collections) {
1104
1482
  this.registry.addCollections(config.collections);
1105
1483
  }
1106
1484
  if (config.globals) {
1107
1485
  this.registry.addGlobals(config.globals);
1108
1486
  }
1487
+ for (const plugin of builtinStoragePlugins) {
1488
+ this.registry.addPlugin(plugin);
1489
+ }
1109
1490
  if (config.plugins) {
1110
1491
  for (const plugin of config.plugins) {
1111
1492
  this.registry.addPlugin(plugin);
@@ -1114,6 +1495,17 @@ var Kyro = class {
1114
1495
  }
1115
1496
  async init() {
1116
1497
  await this.registry.init();
1498
+ const storageGlobal = createStorageSettingsGlobal(
1499
+ this.registry.storageProviders,
1500
+ (name) => this.registry.storageProviders.isPluginEnabled(name)
1501
+ );
1502
+ this.registry.addGlobalPostInit(storageGlobal);
1503
+ const pluginSettingsGlobal = {
1504
+ slug: "plugin-settings",
1505
+ admin: { hidden: true },
1506
+ fields: [{ name: "states", type: "json" }]
1507
+ };
1508
+ this.registry.addGlobalPostInit(pluginSettingsGlobal);
1117
1509
  if (!this.db) {
1118
1510
  throw new Error(
1119
1511
  `Database adapter is null \u2014 failed to load at startup. Check the server console for the exact error.`
@@ -1160,6 +1552,7 @@ var Kyro = class {
1160
1552
  { name: "nextRetryAt", type: "date" }
1161
1553
  ]
1162
1554
  };
1555
+ const allGlobals = this.registry.getGlobals();
1163
1556
  await this.db.init(
1164
1557
  [
1165
1558
  ...this.registry.getCollections(),
@@ -1167,12 +1560,13 @@ var Kyro = class {
1167
1560
  webhookCollection,
1168
1561
  webhookDeliveryCollection
1169
1562
  ],
1170
- this.registry.getGlobals()
1563
+ allGlobals
1171
1564
  );
1565
+ await this.loadPluginState();
1172
1566
  this.pubsub.autoRegisterHooks();
1173
1567
  console.log("\u2705 Kyro CMS initialized");
1174
1568
  console.log(` Collections: ${this.registry.getCollections().length}`);
1175
- console.log(` Globals: ${this.registry.getGlobals().length}`);
1569
+ console.log(` Globals: ${allGlobals.length}`);
1176
1570
  }
1177
1571
  // ============================================================================
1178
1572
  // API Methods
@@ -1181,18 +1575,38 @@ var Kyro = class {
1181
1575
  async loadSettings() {
1182
1576
  if (this.settings) return this.settings;
1183
1577
  try {
1184
- const accessSettings = await this.db.findOne({
1185
- collection: "_globals",
1186
- where: { slug: "access-settings" }
1578
+ const doc = await this.db.findOne({
1579
+ collection: "_globals_access-settings",
1580
+ where: {}
1187
1581
  });
1188
- if (accessSettings) {
1189
- this.settings = accessSettings.data;
1582
+ if (doc) {
1583
+ this.settings = { access: doc };
1190
1584
  }
1191
1585
  } catch (e) {
1192
1586
  console.log("\u26A0\uFE0F No access-settings found, using defaults");
1193
1587
  }
1194
1588
  return this.settings || {};
1195
1589
  }
1590
+ async loadPluginState() {
1591
+ const storageRegistry = this.registry.storageProviders;
1592
+ const pluginNames = storageRegistry.getAllPluginNames();
1593
+ let pluginStates = {};
1594
+ try {
1595
+ const doc = await this.db.findOne({
1596
+ collection: "_globals_plugin-settings",
1597
+ where: {}
1598
+ });
1599
+ if (doc && doc.states) {
1600
+ pluginStates = doc.states;
1601
+ }
1602
+ } catch (e) {
1603
+ }
1604
+ for (const name of pluginNames) {
1605
+ if (pluginStates[name] !== void 0) {
1606
+ storageRegistry.setPluginEnabled(name, pluginStates[name]);
1607
+ }
1608
+ }
1609
+ }
1196
1610
  getREST(options) {
1197
1611
  const authObj = typeof this.config.auth === "object" ? this.config.auth : null;
1198
1612
  const authSecret = authObj?.secret;
@@ -1210,33 +1624,196 @@ var Kyro = class {
1210
1624
  });
1211
1625
  }
1212
1626
  getGraphQL(options) {
1213
- return buildGraphQLSchema({
1627
+ const defaultSchema = buildGraphQLSchema({
1214
1628
  registry: this.registry,
1215
1629
  db: this.db,
1216
- ...options,
1217
- settings: this.settings
1630
+ settings: this.settings,
1631
+ user: options?.user,
1632
+ req: options?.req,
1633
+ tenantID: options?.tenantID
1218
1634
  });
1635
+ return {
1636
+ fetch: async (request, _locals) => {
1637
+ const apiKeyRaw = extractApiKeyFromRequest(request);
1638
+ let gqlUser;
1639
+ let apiKeyCtx;
1640
+ if (apiKeyRaw && this.db) {
1641
+ const apiKeyResult = await validateApiKey(apiKeyRaw, this.db);
1642
+ if (!apiKeyResult.valid) {
1643
+ return new Response(
1644
+ JSON.stringify({ errors: [{ message: apiKeyResult.error || "Invalid API key" }] }),
1645
+ { status: 401, headers: { "Content-Type": "application/json" } }
1646
+ );
1647
+ }
1648
+ if (apiKeyResult.user) {
1649
+ gqlUser = apiKeyResult.user;
1650
+ apiKeyCtx = createApiKeyContext(apiKeyResult);
1651
+ }
1652
+ }
1653
+ const mustRebuild = gqlUser !== options?.user || apiKeyCtx !== void 0;
1654
+ const schema = mustRebuild ? buildGraphQLSchema({
1655
+ registry: this.registry,
1656
+ db: this.db,
1657
+ settings: this.settings,
1658
+ user: gqlUser,
1659
+ req: request,
1660
+ tenantID: options?.tenantID,
1661
+ apiKey: apiKeyCtx
1662
+ }) : defaultSchema;
1663
+ const body = request.method === "POST" ? await request.json().catch(() => ({})) : {};
1664
+ const query = body.query || "";
1665
+ const variables = body.variables || {};
1666
+ if (!query) {
1667
+ return new Response(
1668
+ JSON.stringify({ error: "No GraphQL query provided" }),
1669
+ { status: 400, headers: { "Content-Type": "application/json" } }
1670
+ );
1671
+ }
1672
+ try {
1673
+ const document = parse(query);
1674
+ const result = await execute({
1675
+ schema,
1676
+ document,
1677
+ variableValues: variables,
1678
+ contextValue: {
1679
+ db: this.db,
1680
+ registry: this.registry,
1681
+ settings: this.settings,
1682
+ user: gqlUser,
1683
+ req: request,
1684
+ tenantID: options?.tenantID
1685
+ }
1686
+ });
1687
+ return new Response(JSON.stringify(result), {
1688
+ status: 200,
1689
+ headers: { "Content-Type": "application/json" }
1690
+ });
1691
+ } catch (err) {
1692
+ return new Response(
1693
+ JSON.stringify({ errors: [{ message: err.message }] }),
1694
+ { status: 400, headers: { "Content-Type": "application/json" } }
1695
+ );
1696
+ }
1697
+ },
1698
+ schema: defaultSchema
1699
+ };
1219
1700
  }
1220
1701
  getTRPC(options) {
1221
- return createKyroServer({
1222
- registry: this.registry,
1223
- db: this.db,
1224
- req: options?.req || { headers: {} },
1225
- ...options,
1226
- settings: this.settings
1227
- });
1702
+ return {
1703
+ fetch: async (request, locals) => {
1704
+ const url = new URL(request.url);
1705
+ const path = url.pathname.replace(/^\/api\/trpc\//, "");
1706
+ const [slug, ...rest] = path.split(".");
1707
+ let procedureName = rest.join(".");
1708
+ procedureName = procedureName.replace(/\.(query|mutate|subscribe)$/, "");
1709
+ if (!slug || !procedureName) {
1710
+ return new Response(
1711
+ JSON.stringify({
1712
+ error: {
1713
+ message: "Invalid tRPC path",
1714
+ code: -32600,
1715
+ data: { code: "BAD_REQUEST", httpStatus: 400 }
1716
+ }
1717
+ }),
1718
+ { status: 400, headers: { "Content-Type": "application/json" } }
1719
+ );
1720
+ }
1721
+ const ctx = await createContext({
1722
+ db: this.db,
1723
+ registry: this.registry,
1724
+ req: request,
1725
+ user: options?.user,
1726
+ tenantID: options?.tenantID,
1727
+ settings: this.settings
1728
+ });
1729
+ const kyroRouter = createKyroServer(ctx);
1730
+ const collectionRouter = kyroRouter[slug];
1731
+ if (!collectionRouter) {
1732
+ return new Response(
1733
+ JSON.stringify({
1734
+ error: {
1735
+ message: `Collection '${slug}' not found`,
1736
+ code: -32601,
1737
+ data: { code: "NOT_FOUND", httpStatus: 404 }
1738
+ }
1739
+ }),
1740
+ { status: 404, headers: { "Content-Type": "application/json" } }
1741
+ );
1742
+ }
1743
+ const procedure = collectionRouter[procedureName];
1744
+ if (typeof procedure !== "function") {
1745
+ return new Response(
1746
+ JSON.stringify({
1747
+ error: {
1748
+ message: `Procedure '${procedureName}' not found`,
1749
+ code: -32601,
1750
+ data: { code: "NOT_FOUND", httpStatus: 404 }
1751
+ }
1752
+ }),
1753
+ { status: 404, headers: { "Content-Type": "application/json" } }
1754
+ );
1755
+ }
1756
+ try {
1757
+ let raw = {};
1758
+ if (request.method === "POST" || request.method === "PATCH") {
1759
+ raw = await request.json().catch(() => ({}));
1760
+ } else {
1761
+ const qs = new URL(request.url).searchParams.get("input");
1762
+ if (qs) {
1763
+ try {
1764
+ raw = JSON.parse(decodeURIComponent(qs));
1765
+ } catch {
1766
+ }
1767
+ }
1768
+ }
1769
+ const input = raw?.["0"] ?? raw;
1770
+ const result = await procedure({ ...input, collection: slug });
1771
+ return new Response(JSON.stringify({ result: { data: result } }), {
1772
+ status: 200,
1773
+ headers: { "Content-Type": "application/json" }
1774
+ });
1775
+ } catch (err) {
1776
+ const msg = err.message || "Internal error";
1777
+ const httpStatus = msg.includes("not found") ? 404 : msg.includes("denied") || msg.includes("authentication required") ? 403 : msg.includes("conflict") ? 409 : 500;
1778
+ const code = httpStatus === 404 ? -32601 : httpStatus === 403 ? -32001 : httpStatus === 409 ? -32002 : -32603;
1779
+ return new Response(
1780
+ JSON.stringify({
1781
+ error: {
1782
+ message: msg,
1783
+ code,
1784
+ data: {
1785
+ code: "INTERNAL_SERVER_ERROR",
1786
+ httpStatus
1787
+ }
1788
+ }
1789
+ }),
1790
+ { status: httpStatus, headers: { "Content-Type": "application/json" } }
1791
+ );
1792
+ }
1793
+ },
1794
+ router: null
1795
+ };
1796
+ }
1797
+ getWS() {
1798
+ return this.wsServer;
1228
1799
  }
1229
1800
  async startWebSocket(options) {
1230
1801
  const apiAccess = this.settings?.access?.apiAccess;
1231
- if (apiAccess?.websocketEnabled === false) {
1802
+ if (apiAccess?.wsEnabled === false) {
1232
1803
  console.log("\u26A0\uFE0F WebSocket is disabled in settings");
1233
1804
  return null;
1234
1805
  }
1806
+ const defaultVerifyToken = async (token) => {
1807
+ const result = await validateApiKey(token, this.db);
1808
+ if (!result.valid) throw new Error(result.error || "Invalid API key");
1809
+ if (!result.user) throw new Error("API key has no associated user");
1810
+ return result.user;
1811
+ };
1235
1812
  this.wsServer = createWSServer({
1236
1813
  pubsub: this.pubsub,
1237
1814
  port: options?.port || 8080,
1238
1815
  requireAuth: options?.requireAuth ?? apiAccess?.requireAuth,
1239
- verifyToken: options?.verifyToken
1816
+ verifyToken: options?.verifyToken || defaultVerifyToken
1240
1817
  });
1241
1818
  console.log(`\u{1F50C} WebSocket server started on port ${options?.port || 8080}`);
1242
1819
  return this.wsServer;
@@ -1359,15 +1936,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1359
1936
  db;
1360
1937
  path;
1361
1938
  migrations = /* @__PURE__ */ new Map();
1362
- draftsTableName = "kyro_drafts";
1363
1939
  versionsTableName = "kyro_versions";
1364
- tenantContext;
1365
- setTenantContext(context) {
1366
- this.tenantContext = context;
1367
- }
1368
- getTenantContext() {
1369
- return this.tenantContext;
1370
- }
1371
1940
  constructor(options) {
1372
1941
  super();
1373
1942
  this.path = options.path;
@@ -1381,6 +1950,34 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1381
1950
  if (!this.db) {
1382
1951
  this.db = new DatabaseSync(this.path || ":memory:");
1383
1952
  }
1953
+ if (this.db && typeof this.db.prepare === "function" && !this.db.prepare.__wrapped) {
1954
+ const originalPrepare = this.db.prepare.bind(this.db);
1955
+ const wrappedPrepare = (sql) => {
1956
+ const stmt = originalPrepare(sql);
1957
+ const serialize = (val) => {
1958
+ if (typeof val === "boolean") return val ? 1 : 0;
1959
+ if (val === void 0) return null;
1960
+ return val;
1961
+ };
1962
+ return new Proxy(stmt, {
1963
+ get(target, prop, receiver) {
1964
+ if (prop === "all") {
1965
+ return (...params) => target.all(...params.map(serialize));
1966
+ }
1967
+ if (prop === "get") {
1968
+ return (...params) => target.get(...params.map(serialize));
1969
+ }
1970
+ if (prop === "run") {
1971
+ return (...params) => target.run(...params.map(serialize));
1972
+ }
1973
+ const val = Reflect.get(target, prop, receiver);
1974
+ return typeof val === "function" ? val.bind(target) : val;
1975
+ }
1976
+ });
1977
+ };
1978
+ wrappedPrepare.__wrapped = true;
1979
+ this.db.prepare = wrappedPrepare;
1980
+ }
1384
1981
  this.db.exec("PRAGMA journal_mode = WAL");
1385
1982
  this.db.exec("PRAGMA foreign_keys = ON");
1386
1983
  this.connected = true;
@@ -1408,8 +2005,8 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1408
2005
  }
1409
2006
  columns.push(`${this.col("createdAt")} TEXT DEFAULT (datetime('now'))`);
1410
2007
  columns.push(`${this.col("updatedAt")} TEXT DEFAULT (datetime('now'))`);
1411
- columns.push(`_status TEXT DEFAULT 'published'`);
1412
- columns.push(`_has_draft INTEGER DEFAULT 0`);
2008
+ columns.push(`status TEXT DEFAULT 'draft'`);
2009
+ columns.push(`hasDraft INTEGER DEFAULT 0`);
1413
2010
  if (config.tenantScoped) {
1414
2011
  columns.push(`tenant_id TEXT NOT NULL`);
1415
2012
  }
@@ -1417,7 +2014,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1417
2014
  if (existingColumns.length === 0) {
1418
2015
  const createSQL = `CREATE TABLE IF NOT EXISTS ${name} (${columns.join(", ")})`;
1419
2016
  this.db.exec(createSQL);
1420
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${name}__status ON ${name}(_status)`);
2017
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${name}_status ON ${name}(status)`);
1421
2018
  for (const field of flattenFields(config.fields)) {
1422
2019
  if (field.name && field.indexed) {
1423
2020
  this.db.exec(
@@ -1436,9 +2033,9 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1436
2033
  const colName = colDef.split(" ")[0].replace(/^"/, "").replace(/"$/, "");
1437
2034
  if (!existingSet.has(colName) && colName !== "id") {
1438
2035
  try {
1439
- if (colName === "_status") {
2036
+ if (colName === "status") {
1440
2037
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} TEXT DEFAULT 'published'`);
1441
- } else if (colName === "_has_draft") {
2038
+ } else if (colName === "hasDraft") {
1442
2039
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} INTEGER DEFAULT 0`);
1443
2040
  } else {
1444
2041
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} TEXT`);
@@ -1459,6 +2056,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1459
2056
  tenant_id TEXT,
1460
2057
  version INTEGER NOT NULL,
1461
2058
  status TEXT NOT NULL DEFAULT 'draft',
2059
+ autosave INTEGER NOT NULL DEFAULT 0,
1462
2060
  data TEXT NOT NULL,
1463
2061
  created_by TEXT,
1464
2062
  change_description TEXT,
@@ -1473,24 +2071,13 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1473
2071
  this.db.exec(
1474
2072
  `CREATE INDEX IF NOT EXISTS idx_${this.versionsTableName}_status ON ${this.versionsTableName}(status)`
1475
2073
  );
1476
- }
1477
- ensureDraftsTable() {
1478
- this.db.exec(`
1479
- CREATE TABLE IF NOT EXISTS ${this.draftsTableName} (
1480
- id TEXT PRIMARY KEY,
1481
- collection_slug TEXT NOT NULL,
1482
- document_id TEXT NOT NULL,
1483
- tenant_id TEXT,
1484
- data TEXT NOT NULL,
1485
- base_updated_at TEXT,
1486
- draft_updated_at TEXT NOT NULL,
1487
- created_at TEXT DEFAULT (datetime('now')),
1488
- updated_at TEXT DEFAULT (datetime('now'))
1489
- )
1490
- `);
1491
2074
  this.db.exec(
1492
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_${this.draftsTableName}_document ON ${this.draftsTableName}(collection_slug, document_id, tenant_id)`
2075
+ `CREATE INDEX IF NOT EXISTS idx_${this.versionsTableName}_autosave ON ${this.versionsTableName}(autosave)`
1493
2076
  );
2077
+ try {
2078
+ this.db.exec(`ALTER TABLE ${this.versionsTableName} ADD COLUMN autosave INTEGER NOT NULL DEFAULT 0`);
2079
+ } catch {
2080
+ }
1494
2081
  }
1495
2082
  // ========================================================================
1496
2083
  // SQL Quoting
@@ -1571,8 +2158,8 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1571
2158
  const rlsQuery = applyRLS({ where: effectiveWhere }, slug, this.tenantContext, DEFAULT_RLS_CONFIG);
1572
2159
  effectiveWhere = rlsQuery.where || {};
1573
2160
  }
1574
- if (!draft && config.versions?.drafts) {
1575
- conditions.push(`_status = ?`);
2161
+ if (!draft) {
2162
+ conditions.push(`status = ?`);
1576
2163
  params.push("published");
1577
2164
  }
1578
2165
  if (tenantID && config.tenantScoped) {
@@ -1617,16 +2204,10 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1617
2204
  }
1618
2205
  if (draft) {
1619
2206
  docs = await Promise.all(docs.map(async (doc) => {
1620
- if (doc._has_draft) {
1621
- const versions = await this.findVersions({
1622
- collection: slug,
1623
- documentId: doc.id,
1624
- limit: 1,
1625
- sort: "-createdAt"
1626
- });
1627
- if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1628
- return { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
1629
- }
2207
+ const version = this.db.prepare(`SELECT * FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? AND tenant_id IS NULL ORDER BY version DESC LIMIT 1`).get(slug, doc.id);
2208
+ if (version) {
2209
+ const versionData = version.data ? JSON.parse(version.data) : {};
2210
+ return { ...doc, ...versionData, status: doc.status };
1630
2211
  }
1631
2212
  return doc;
1632
2213
  }));
@@ -1658,8 +2239,8 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1658
2239
  return null;
1659
2240
  }
1660
2241
  }
1661
- if (!draft && config.versions?.drafts) {
1662
- sql += ` AND _status = ?`;
2242
+ if (!draft) {
2243
+ sql += ` AND status = ?`;
1663
2244
  params.push("published");
1664
2245
  }
1665
2246
  if (tenantID && config.tenantScoped) {
@@ -1669,15 +2250,11 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1669
2250
  const row = this.db.prepare(sql).get(...params);
1670
2251
  if (!row) return null;
1671
2252
  let doc = this.rowToDoc(row, config);
1672
- if (draft && doc._has_draft) {
1673
- const versions = await this.findVersions({
1674
- collection: slug,
1675
- documentId: doc.id,
1676
- limit: 1,
1677
- sort: "-createdAt"
1678
- });
1679
- if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1680
- doc = { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
2253
+ if (draft) {
2254
+ const version = this.db.prepare(`SELECT * FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? AND tenant_id IS NULL ORDER BY version DESC LIMIT 1`).get(slug, doc.id);
2255
+ if (version) {
2256
+ const versionData = version.data ? JSON.parse(version.data) : {};
2257
+ doc = { ...doc, ...versionData, status: doc.status };
1681
2258
  }
1682
2259
  }
1683
2260
  return doc;
@@ -1708,7 +2285,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1708
2285
  const quotedColumns = filteredColumns.map((c) => this.col(c));
1709
2286
  const placeholders = filteredColumns.map(() => "?").join(", ");
1710
2287
  const values = Object.values(filteredData).map(
1711
- (v) => typeof v === "object" ? JSON.stringify(v) : v
2288
+ (v) => v !== null && typeof v === "object" ? JSON.stringify(v) : v
1712
2289
  );
1713
2290
  this.db.prepare(
1714
2291
  `INSERT OR REPLACE INTO ${tableName} (${quotedColumns.join(", ")}) VALUES (${placeholders})`
@@ -1788,7 +2365,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1788
2365
  const conditions = [];
1789
2366
  const params = [];
1790
2367
  if (!args.draft && globalConfig.versions) {
1791
- conditions.push("_status = 'published'");
2368
+ conditions.push("status = 'published'");
1792
2369
  }
1793
2370
  if (conditions.length > 0) {
1794
2371
  sql += ` WHERE ${conditions.join(" AND ")}`;
@@ -1797,15 +2374,11 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1797
2374
  const result2 = this.db.prepare(sql).get(...params);
1798
2375
  if (result2) {
1799
2376
  let doc = this.rowToDoc(result2, globalConfig);
1800
- if (args.draft && doc._has_draft) {
1801
- const versions = await this.findVersions({
1802
- collection: args.collection,
1803
- documentId: parsed.globalSlug,
1804
- limit: 1,
1805
- sort: "-createdAt"
1806
- });
1807
- if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1808
- doc = { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
2377
+ if (args.draft) {
2378
+ const version = this.db.prepare(`SELECT * FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? AND tenant_id IS NULL ORDER BY version DESC LIMIT 1`).get(args.collection, parsed.globalSlug);
2379
+ if (version) {
2380
+ const versionData = version.data ? JSON.parse(version.data) : {};
2381
+ doc = { ...doc, ...versionData, status: doc.status };
1809
2382
  }
1810
2383
  }
1811
2384
  return doc;
@@ -1821,7 +2394,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1821
2394
  async findVersions(args) {
1822
2395
  this.ensureVersionsTable();
1823
2396
  const { collection, documentId, tenantID, limit = 20, page = 1 } = args;
1824
- const conditions = [`collection_slug = ?`, `document_id = ?`];
2397
+ const conditions = [`collection_slug = ?`, `document_id = ?`, `autosave = 0`];
1825
2398
  const params = [collection, documentId];
1826
2399
  if (tenantID) {
1827
2400
  conditions.push(`tenant_id = ?`);
@@ -1856,13 +2429,30 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1856
2429
  async createVersion(args) {
1857
2430
  this.ensureVersionsTable();
1858
2431
  const now = (/* @__PURE__ */ new Date()).toISOString();
2432
+ if (args.autosave) {
2433
+ let sql = `SELECT * FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? AND autosave = 1`;
2434
+ const params = [args.collection, args.documentId];
2435
+ if (args.tenantID) {
2436
+ sql += ` AND tenant_id = ?`;
2437
+ params.push(args.tenantID);
2438
+ } else {
2439
+ sql += ` AND tenant_id IS NULL`;
2440
+ }
2441
+ sql += ` LIMIT 1`;
2442
+ const existing = this.db.prepare(sql).get(...params);
2443
+ if (existing) {
2444
+ this.db.prepare(`UPDATE ${this.versionsTableName} SET data = ?, status = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(args.data), args.status, now, existing.id);
2445
+ const result = await this.findVersionByID({ collection: args.collection, versionId: existing.id });
2446
+ if (result) return result;
2447
+ }
2448
+ }
1859
2449
  const id = this.generateId();
1860
- const latestRow = this.db.prepare(`SELECT version FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? ORDER BY version DESC LIMIT 1`).get(args.collection, args.documentId);
2450
+ const latestRow = this.db.prepare(`SELECT version FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? AND autosave = 0 ORDER BY version DESC LIMIT 1`).get(args.collection, args.documentId);
1861
2451
  const nextVersion = (latestRow?.version ?? 0) + 1;
1862
2452
  this.db.prepare(
1863
2453
  `INSERT INTO ${this.versionsTableName} (
1864
- id, collection_slug, document_id, tenant_id, version, status, data, created_by, change_description, published_at, created_at, updated_at
1865
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2454
+ id, collection_slug, document_id, tenant_id, version, status, autosave, data, created_by, change_description, published_at, created_at, updated_at
2455
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
1866
2456
  ).run(
1867
2457
  id,
1868
2458
  args.collection,
@@ -1870,6 +2460,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1870
2460
  args.tenantID ?? null,
1871
2461
  nextVersion,
1872
2462
  args.status,
2463
+ args.autosave ? 1 : 0,
1873
2464
  JSON.stringify(args.data),
1874
2465
  args.createdBy ?? null,
1875
2466
  args.changeDescription ?? null,
@@ -1877,23 +2468,28 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1877
2468
  now,
1878
2469
  now
1879
2470
  );
1880
- const collectionConfig = this.collections.get(args.collection);
1881
- const maxPerDoc = collectionConfig?.versions?.maxPerDoc;
1882
- if (maxPerDoc && maxPerDoc > 0) {
1883
- await this.deleteVersions({ collection: args.collection, documentId: args.documentId, keepLatest: maxPerDoc, tenantID: args.tenantID });
2471
+ if (!args.autosave) {
2472
+ const collectionConfig = this.collections.get(args.collection);
2473
+ const maxPerDoc = collectionConfig?.versions?.maxPerDoc;
2474
+ if (maxPerDoc && maxPerDoc > 0) {
2475
+ await this.deleteVersions({ collection: args.collection, documentId: args.documentId, keepLatest: maxPerDoc, tenantID: args.tenantID });
2476
+ }
1884
2477
  }
1885
2478
  const saved = await this.findVersionByID({ collection: args.collection, versionId: id });
1886
2479
  return saved;
1887
2480
  }
2481
+ async updateLatestVersion(args) {
2482
+ return this.createVersion({ ...args, autosave: true });
2483
+ }
1888
2484
  async deleteVersions(args) {
1889
2485
  this.ensureVersionsTable();
1890
2486
  const { collection, documentId, keepLatest, tenantID } = args;
1891
2487
  if (keepLatest && keepLatest > 0) {
1892
- const rows = this.db.prepare(`SELECT id, status FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? ORDER BY version DESC`).all(collection, documentId);
2488
+ const rows = this.db.prepare(`SELECT id, status, autosave FROM ${this.versionsTableName} WHERE collection_slug = ? AND document_id = ? ORDER BY version DESC`).all(collection, documentId);
1893
2489
  let draftCount = 0;
1894
2490
  const toDelete = [];
1895
2491
  for (const row of rows) {
1896
- if (row.status === "published") continue;
2492
+ if (row.status === "published" || row.autosave === 1) continue;
1897
2493
  draftCount++;
1898
2494
  if (draftCount > keepLatest) toDelete.push(row.id);
1899
2495
  }
@@ -1925,60 +2521,6 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1925
2521
  updatedAt: row.updated_at
1926
2522
  };
1927
2523
  }
1928
- async findDraft(args) {
1929
- this.ensureDraftsTable();
1930
- let sql = `SELECT * FROM ${this.draftsTableName} WHERE collection_slug = ? AND document_id = ?`;
1931
- const params = [args.collection, args.documentId];
1932
- if (args.tenantID) {
1933
- sql += ` AND tenant_id = ?`;
1934
- params.push(args.tenantID);
1935
- } else {
1936
- sql += ` AND tenant_id IS NULL`;
1937
- }
1938
- sql += ` LIMIT 1`;
1939
- const row = this.db.prepare(sql).get(...params);
1940
- if (!row) return null;
1941
- return this.rowToDraft(row);
1942
- }
1943
- async upsertDraft(args) {
1944
- this.ensureDraftsTable();
1945
- const existing = await this.findDraft(args);
1946
- const now = (/* @__PURE__ */ new Date()).toISOString();
1947
- const draftUpdatedAt = args.draftUpdatedAt || now;
1948
- const id = existing?.id || this.generateId();
1949
- this.db.prepare(
1950
- `INSERT OR REPLACE INTO ${this.draftsTableName} (
1951
- id, collection_slug, document_id, tenant_id, data, base_updated_at, draft_updated_at, created_at, updated_at
1952
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
1953
- ).run(
1954
- id,
1955
- args.collection,
1956
- args.documentId,
1957
- args.tenantID ?? null,
1958
- JSON.stringify(args.data),
1959
- args.baseUpdatedAt ?? null,
1960
- draftUpdatedAt,
1961
- existing?.createdAt || now,
1962
- now
1963
- );
1964
- const saved = await this.findDraft(args);
1965
- if (!saved) {
1966
- throw new Error("Failed to persist draft snapshot");
1967
- }
1968
- return saved;
1969
- }
1970
- async deleteDraft(args) {
1971
- this.ensureDraftsTable();
1972
- let sql = `DELETE FROM ${this.draftsTableName} WHERE collection_slug = ? AND document_id = ?`;
1973
- const params = [args.collection, args.documentId];
1974
- if (args.tenantID) {
1975
- sql += ` AND tenant_id = ?`;
1976
- params.push(args.tenantID);
1977
- } else {
1978
- sql += ` AND tenant_id IS NULL`;
1979
- }
1980
- this.db.prepare(sql).run(...params);
1981
- }
1982
2524
  // ========================================================================
1983
2525
  // Helpers
1984
2526
  // ========================================================================
@@ -2147,6 +2689,8 @@ var LocalAdapter = class extends AbstractBaseAdapter {
2147
2689
  if (config.tenantScoped) {
2148
2690
  doc.tenantID = row.tenant_id;
2149
2691
  }
2692
+ doc.status = row.status ?? "published";
2693
+ doc.hasDraft = row.hasDraft ? Boolean(row.hasDraft) : false;
2150
2694
  return doc;
2151
2695
  }
2152
2696
  generateId() {
@@ -2172,19 +2716,6 @@ var LocalAdapter = class extends AbstractBaseAdapter {
2172
2716
  getTableNameFor(slug) {
2173
2717
  return slug.replace(/-/g, "_");
2174
2718
  }
2175
- rowToDraft(row) {
2176
- return {
2177
- id: row.id,
2178
- collection: row.collection_slug,
2179
- documentId: row.document_id,
2180
- tenantID: row.tenant_id ?? void 0,
2181
- data: row.data ? JSON.parse(row.data) : {},
2182
- baseUpdatedAt: row.base_updated_at ?? null,
2183
- draftUpdatedAt: row.draft_updated_at,
2184
- createdAt: row.created_at,
2185
- updatedAt: row.updated_at
2186
- };
2187
- }
2188
2719
  // ========================================================================
2189
2720
  // Migrations
2190
2721
  // ========================================================================
@@ -2192,7 +2723,6 @@ var LocalAdapter = class extends AbstractBaseAdapter {
2192
2723
  for (const config of this.collections.values()) {
2193
2724
  this.ensureTable(config);
2194
2725
  }
2195
- this.ensureDraftsTable();
2196
2726
  console.log("[LocalAdapter] Migrations complete");
2197
2727
  }
2198
2728
  async rollback() {
@@ -2232,5 +2762,5 @@ function createLocalAdapter(options) {
2232
2762
  }
2233
2763
 
2234
2764
  export { ConfigValidationError, Kyro, LocalAdapter, Registry, collectionToCreateZod, collectionToUpdateZod, collectionToWhereZod, collectionToZod, createKyro, createLocalAdapter, createRegistry, fieldToZod, getRegistry, globalToZod, resetRegistry, validateCollection, validateConfig, validateFields, validateGlobal };
2235
- //# sourceMappingURL=chunk-35U3FROB.js.map
2236
- //# sourceMappingURL=chunk-35U3FROB.js.map
2765
+ //# sourceMappingURL=chunk-5H3MWQJS.js.map
2766
+ //# sourceMappingURL=chunk-5H3MWQJS.js.map