@kyro-cms/core 0.8.0 → 0.9.1

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 (248) hide show
  1. package/README.md +57 -589
  2. package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-CUTb9XOy.d.ts} +1 -1
  3. package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-Yg2UEOB4.d.cts} +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 +31 -97
  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 +19 -95
  21. package/dist/api-handler.js.map +1 -1
  22. package/dist/{tenant-B1YB0Jy8.d.ts → base-B71y_EAF.d.cts} +6 -12
  23. package/dist/{tenant-Cpeveji6.d.cts → base-DaqY2GhA.d.ts} +6 -12
  24. package/dist/bootstrap-5NLASFOG.cjs +32 -0
  25. package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-5NLASFOG.cjs.map} +1 -1
  26. package/dist/bootstrap-T5BK77LD.js +7 -0
  27. package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-T5BK77LD.js.map} +1 -1
  28. package/dist/{chunk-35U3FROB.js → chunk-22M4O4ZJ.js} +607 -63
  29. package/dist/chunk-22M4O4ZJ.js.map +1 -0
  30. package/dist/chunk-2HZRBATX.cjs +253 -0
  31. package/dist/chunk-2HZRBATX.cjs.map +1 -0
  32. package/dist/{chunk-VJT6P4N6.cjs → chunk-3HR772HI.cjs} +199 -32
  33. package/dist/chunk-3HR772HI.cjs.map +1 -0
  34. package/dist/chunk-3KTWGODI.cjs +178 -0
  35. package/dist/chunk-3KTWGODI.cjs.map +1 -0
  36. package/dist/{chunk-QXIQWPAP.js → chunk-3UK5XBVJ.js} +4 -134
  37. package/dist/chunk-3UK5XBVJ.js.map +1 -0
  38. package/dist/{chunk-FXYP2HA6.js → chunk-4AO3A3JM.js} +48 -4
  39. package/dist/chunk-4AO3A3JM.js.map +1 -0
  40. package/dist/chunk-4M7X5HAB.cjs +173 -0
  41. package/dist/chunk-4M7X5HAB.cjs.map +1 -0
  42. package/dist/chunk-5EPFQUQD.js +3243 -0
  43. package/dist/chunk-5EPFQUQD.js.map +1 -0
  44. package/dist/{chunk-Y3N7UUDO.js → chunk-7OGPN7MP.js} +5 -2
  45. package/dist/chunk-7OGPN7MP.js.map +1 -0
  46. package/dist/{chunk-WOWUL7ZY.js → chunk-AL5KX63J.js} +4 -3
  47. package/dist/chunk-AL5KX63J.js.map +1 -0
  48. package/dist/{chunk-2OL4O2TH.cjs → chunk-C36TMDTY.cjs} +66 -61
  49. package/dist/chunk-C36TMDTY.cjs.map +1 -0
  50. package/dist/{chunk-ES5HNFFT.js → chunk-CF7OL6HR.js} +4 -2
  51. package/dist/chunk-CF7OL6HR.js.map +1 -0
  52. package/dist/chunk-CJONKRHJ.js +162 -0
  53. package/dist/chunk-CJONKRHJ.js.map +1 -0
  54. package/dist/{chunk-2KVHZE6O.cjs → chunk-COIASRDK.cjs} +202 -46
  55. package/dist/chunk-COIASRDK.cjs.map +1 -0
  56. package/dist/chunk-DEVFAKCQ.cjs +3291 -0
  57. package/dist/chunk-DEVFAKCQ.cjs.map +1 -0
  58. package/dist/{chunk-3ZFYL34R.js → chunk-DYTZ6FQ7.js} +12 -185
  59. package/dist/chunk-DYTZ6FQ7.js.map +1 -0
  60. package/dist/{chunk-QPPDLRNR.js → chunk-EJN2PAOE.js} +197 -41
  61. package/dist/chunk-EJN2PAOE.js.map +1 -0
  62. package/dist/chunk-FAXU7BMP.js +220 -0
  63. package/dist/chunk-FAXU7BMP.js.map +1 -0
  64. package/dist/{chunk-OHVB4AJ7.js → chunk-FOPGUM27.js} +22 -17
  65. package/dist/chunk-FOPGUM27.js.map +1 -0
  66. package/dist/chunk-GAOXD3XT.js +175 -0
  67. package/dist/chunk-GAOXD3XT.js.map +1 -0
  68. package/dist/{chunk-4DA7QPLA.cjs → chunk-GXFOGU7N.cjs} +5 -2
  69. package/dist/chunk-GXFOGU7N.cjs.map +1 -0
  70. package/dist/{chunk-I7HHI6QV.cjs → chunk-IDVRRRAK.cjs} +17 -9
  71. package/dist/chunk-IDVRRRAK.cjs.map +1 -0
  72. package/dist/{chunk-WQBRWOQT.cjs → chunk-JOPVMWTM.cjs} +3 -2
  73. package/dist/chunk-JOPVMWTM.cjs.map +1 -0
  74. package/dist/chunk-KC2GDBLS.cjs +84 -0
  75. package/dist/chunk-KC2GDBLS.cjs.map +1 -0
  76. package/dist/{chunk-K7JPTH3G.cjs → chunk-KNRSROWB.cjs} +132 -74
  77. package/dist/chunk-KNRSROWB.cjs.map +1 -0
  78. package/dist/{chunk-3AJE4SEG.js → chunk-KPA4AN4R.js} +125 -67
  79. package/dist/chunk-KPA4AN4R.js.map +1 -0
  80. package/dist/{chunk-QUW2RZTM.cjs → chunk-L46ROHUS.cjs} +51 -7
  81. package/dist/chunk-L46ROHUS.cjs.map +1 -0
  82. package/dist/chunk-L4EZKIEX.js +185 -0
  83. package/dist/chunk-L4EZKIEX.js.map +1 -0
  84. package/dist/{chunk-REK7AYOC.js → chunk-L5UKKZQN.js} +199 -32
  85. package/dist/chunk-L5UKKZQN.js.map +1 -0
  86. package/dist/chunk-NKPKR5BW.cjs +188 -0
  87. package/dist/chunk-NKPKR5BW.cjs.map +1 -0
  88. package/dist/{chunk-Y3QQN7PN.js → chunk-P2HKJ7P5.js} +13 -4
  89. package/dist/chunk-P2HKJ7P5.js.map +1 -0
  90. package/dist/{chunk-SA7NSSIQ.cjs → chunk-PI73NNOK.cjs} +13 -187
  91. package/dist/chunk-PI73NNOK.cjs.map +1 -0
  92. package/dist/{chunk-HXRD4B37.js → chunk-PU2Z5VWF.js} +1279 -556
  93. package/dist/chunk-PU2Z5VWF.js.map +1 -0
  94. package/dist/{chunk-H727JIG7.js → chunk-Q72BOAPK.js} +16 -8
  95. package/dist/chunk-Q72BOAPK.js.map +1 -0
  96. package/dist/{chunk-IBG6V56E.cjs → chunk-QFLB4EIJ.cjs} +2 -139
  97. package/dist/chunk-QFLB4EIJ.cjs.map +1 -0
  98. package/dist/{chunk-YVUJBEXE.cjs → chunk-RAMGUDJN.cjs} +16 -7
  99. package/dist/chunk-RAMGUDJN.cjs.map +1 -0
  100. package/dist/{chunk-LINKCEG4.cjs → chunk-ROJHKAQ4.cjs} +617 -73
  101. package/dist/chunk-ROJHKAQ4.cjs.map +1 -0
  102. package/dist/{chunk-5KVM3WEY.cjs → chunk-RSF3UU7H.cjs} +1330 -602
  103. package/dist/chunk-RSF3UU7H.cjs.map +1 -0
  104. package/dist/{chunk-V3LKPM3O.cjs → chunk-SHTTJMLT.cjs} +4 -2
  105. package/dist/chunk-SHTTJMLT.cjs.map +1 -0
  106. package/dist/chunk-SPBTLUN6.js +92 -0
  107. package/dist/chunk-SPBTLUN6.js.map +1 -0
  108. package/dist/{chunk-57P6MJKC.js → chunk-TXSZFA4G.js} +3 -3
  109. package/dist/chunk-TXSZFA4G.js.map +1 -0
  110. package/dist/chunk-UERVXYVK.cjs +99 -0
  111. package/dist/chunk-UERVXYVK.cjs.map +1 -0
  112. package/dist/{chunk-PDYFVNUX.cjs → chunk-V2TVSCV5.cjs} +16 -23
  113. package/dist/chunk-V2TVSCV5.cjs.map +1 -0
  114. package/dist/{chunk-DXHRBMGB.js → chunk-VO35MNPH.js} +12 -19
  115. package/dist/chunk-VO35MNPH.js.map +1 -0
  116. package/dist/{chunk-IA6AU5PI.cjs → chunk-WNCYAKF3.cjs} +3 -3
  117. package/dist/chunk-WNCYAKF3.cjs.map +1 -0
  118. package/dist/chunk-XEB7PH2E.js +81 -0
  119. package/dist/chunk-XEB7PH2E.js.map +1 -0
  120. package/dist/cli/index.cjs +5 -5
  121. package/dist/cli/index.cjs.map +1 -1
  122. package/dist/cli/index.js +5 -5
  123. package/dist/cli/index.js.map +1 -1
  124. package/dist/client.cjs +3 -3
  125. package/dist/client.d.cts +3 -3
  126. package/dist/client.d.ts +3 -3
  127. package/dist/client.js +1 -1
  128. package/dist/drizzle/index.cjs +14 -13
  129. package/dist/drizzle/index.d.cts +9 -7
  130. package/dist/drizzle/index.d.ts +9 -7
  131. package/dist/drizzle/index.js +5 -4
  132. package/dist/fields/index.cjs +21 -37
  133. package/dist/fields/index.d.cts +2 -22
  134. package/dist/fields/index.d.ts +2 -22
  135. package/dist/fields/index.js +1 -1
  136. package/dist/graphql/index.cjs +5 -4
  137. package/dist/graphql/index.d.cts +5 -3
  138. package/dist/graphql/index.d.ts +5 -3
  139. package/dist/graphql/index.js +3 -2
  140. package/dist/index-CJXPB_ot.d.ts +276 -0
  141. package/dist/index-CaTNnLGd.d.cts +276 -0
  142. package/dist/index.cjs +304 -162
  143. package/dist/index.cjs.map +1 -1
  144. package/dist/index.d.cts +129 -205
  145. package/dist/index.d.ts +129 -205
  146. package/dist/index.js +172 -33
  147. package/dist/index.js.map +1 -1
  148. package/dist/integration.cjs +2 -2
  149. package/dist/integration.js +1 -1
  150. package/dist/mongo-auth-adapter-ISOM7FSS.cjs +17 -0
  151. package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-ISOM7FSS.cjs.map} +1 -1
  152. package/dist/mongo-auth-adapter-MO6STCV3.js +4 -0
  153. package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-MO6STCV3.js.map} +1 -1
  154. package/dist/mongodb/index.cjs +8 -7
  155. package/dist/mongodb/index.d.cts +5 -7
  156. package/dist/mongodb/index.d.ts +5 -7
  157. package/dist/mongodb/index.js +4 -3
  158. package/dist/postgres-auth-adapter-DWDR7P5G.js +5 -0
  159. package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-DWDR7P5G.js.map} +1 -1
  160. package/dist/postgres-auth-adapter-WRWSJD4E.cjs +14 -0
  161. package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-WRWSJD4E.cjs.map} +1 -1
  162. package/dist/redis-adapter-HGTPWIGV.js +4 -0
  163. package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-HGTPWIGV.js.map} +1 -1
  164. package/dist/redis-adapter-KJ3YOOT6.cjs +13 -0
  165. package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-KJ3YOOT6.cjs.map} +1 -1
  166. package/dist/rest/index.cjs +15 -14
  167. package/dist/rest/index.d.cts +4 -4
  168. package/dist/rest/index.d.ts +4 -4
  169. package/dist/rest/index.js +13 -12
  170. package/dist/{schema-5PHL5IVB.js → schema-6I5OFR4Z.js} +3 -3
  171. package/dist/{schema-5PHL5IVB.js.map → schema-6I5OFR4Z.js.map} +1 -1
  172. package/dist/{schema-37SE2F4B.cjs → schema-TTFE4467.cjs} +14 -14
  173. package/dist/{schema-37SE2F4B.cjs.map → schema-TTFE4467.cjs.map} +1 -1
  174. package/dist/sqlite-adapter-6GEUSVXQ.js +4 -0
  175. package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-6GEUSVXQ.js.map} +1 -1
  176. package/dist/sqlite-adapter-CSIZE5SX.cjs +13 -0
  177. package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-CSIZE5SX.cjs.map} +1 -1
  178. package/dist/templates/index.cjs +133 -31
  179. package/dist/templates/index.d.cts +52 -9
  180. package/dist/templates/index.d.ts +52 -9
  181. package/dist/templates/index.js +3 -1
  182. package/dist/trpc/index.cjs +13 -12
  183. package/dist/trpc/index.d.cts +55 -49
  184. package/dist/trpc/index.d.ts +55 -49
  185. package/dist/trpc/index.js +4 -3
  186. package/dist/{types-D6ZLRGbH.d.cts → types-CpjuXbe7.d.cts} +2 -0
  187. package/dist/{types-D6ZLRGbH.d.ts → types-CpjuXbe7.d.ts} +2 -0
  188. package/dist/{types-Bs1up4yP.d.ts → types-CyCQ6SAI.d.ts} +28 -2
  189. package/dist/{types-J3R9nVsZ.d.cts → types-DJxD9394.d.cts} +28 -2
  190. package/dist/{types-VtjUxIMp.d.cts → types-Z6FBiqa2.d.cts} +35 -14
  191. package/dist/{types-VtjUxIMp.d.ts → types-Z6FBiqa2.d.ts} +35 -14
  192. package/package.json +22 -4
  193. package/dist/bootstrap-AKAUP6F6.cjs +0 -32
  194. package/dist/bootstrap-JCML6NFO.js +0 -7
  195. package/dist/chunk-2KVHZE6O.cjs.map +0 -1
  196. package/dist/chunk-2OL4O2TH.cjs.map +0 -1
  197. package/dist/chunk-35U3FROB.js.map +0 -1
  198. package/dist/chunk-3AJE4SEG.js.map +0 -1
  199. package/dist/chunk-3J4MFTI3.js +0 -3872
  200. package/dist/chunk-3J4MFTI3.js.map +0 -1
  201. package/dist/chunk-3ZFYL34R.js.map +0 -1
  202. package/dist/chunk-4DA7QPLA.cjs.map +0 -1
  203. package/dist/chunk-57P6MJKC.js.map +0 -1
  204. package/dist/chunk-5KVM3WEY.cjs.map +0 -1
  205. package/dist/chunk-6IMPH6WV.cjs +0 -3897
  206. package/dist/chunk-6IMPH6WV.cjs.map +0 -1
  207. package/dist/chunk-ATBOUGQP.cjs +0 -513
  208. package/dist/chunk-ATBOUGQP.cjs.map +0 -1
  209. package/dist/chunk-DXHRBMGB.js.map +0 -1
  210. package/dist/chunk-ES5HNFFT.js.map +0 -1
  211. package/dist/chunk-FXYP2HA6.js.map +0 -1
  212. package/dist/chunk-H727JIG7.js.map +0 -1
  213. package/dist/chunk-HXRD4B37.js.map +0 -1
  214. package/dist/chunk-I7HHI6QV.cjs.map +0 -1
  215. package/dist/chunk-IA6AU5PI.cjs.map +0 -1
  216. package/dist/chunk-IBG6V56E.cjs.map +0 -1
  217. package/dist/chunk-K7JPTH3G.cjs.map +0 -1
  218. package/dist/chunk-LINKCEG4.cjs.map +0 -1
  219. package/dist/chunk-OHVB4AJ7.js.map +0 -1
  220. package/dist/chunk-PDYFVNUX.cjs.map +0 -1
  221. package/dist/chunk-Q23JB3KL.js +0 -488
  222. package/dist/chunk-Q23JB3KL.js.map +0 -1
  223. package/dist/chunk-QPPDLRNR.js.map +0 -1
  224. package/dist/chunk-QUW2RZTM.cjs.map +0 -1
  225. package/dist/chunk-QXIQWPAP.js.map +0 -1
  226. package/dist/chunk-R3XIBBAW.cjs +0 -34
  227. package/dist/chunk-R3XIBBAW.cjs.map +0 -1
  228. package/dist/chunk-REK7AYOC.js.map +0 -1
  229. package/dist/chunk-SA7NSSIQ.cjs.map +0 -1
  230. package/dist/chunk-SDMNUYVU.js +0 -30
  231. package/dist/chunk-SDMNUYVU.js.map +0 -1
  232. package/dist/chunk-V3LKPM3O.cjs.map +0 -1
  233. package/dist/chunk-VJT6P4N6.cjs.map +0 -1
  234. package/dist/chunk-WOWUL7ZY.js.map +0 -1
  235. package/dist/chunk-WQBRWOQT.cjs.map +0 -1
  236. package/dist/chunk-Y3N7UUDO.js.map +0 -1
  237. package/dist/chunk-Y3QQN7PN.js.map +0 -1
  238. package/dist/chunk-YVUJBEXE.cjs.map +0 -1
  239. package/dist/index-CLp-DRKA.d.ts +0 -64
  240. package/dist/index-DfO7G4kN.d.cts +0 -64
  241. package/dist/mongo-auth-adapter-NHHUJHVH.cjs +0 -17
  242. package/dist/mongo-auth-adapter-NJQUUCTP.js +0 -4
  243. package/dist/postgres-auth-adapter-3T2NKTSE.js +0 -5
  244. package/dist/postgres-auth-adapter-7IEENCKQ.cjs +0 -14
  245. package/dist/redis-adapter-D2E2S3GB.cjs +0 -13
  246. package/dist/redis-adapter-VQXD7ESY.js +0 -4
  247. package/dist/sqlite-adapter-LVK5PS4T.cjs +0 -13
  248. 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';
1
+ import { createStorageSettingsGlobal } from './chunk-GAOXD3XT.js';
2
+ import { createContext, createKyroServer } from './chunk-KPA4AN4R.js';
3
+ import { buildGraphQLSchema } from './chunk-L5UKKZQN.js';
4
+ import { getDefaultRegistry, createHonoApp, createS3Storage, createCloudinaryStorage, createFtpStorage } from './chunk-PU2Z5VWF.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';
5
7
  import { KyroPubSub, createWSServer } from './chunk-3TPQ2BU6.js';
6
- import { AbstractBaseAdapter, applyRLS, DEFAULT_RLS_CONFIG, canAccessDocument } from './chunk-3ZFYL34R.js';
8
+ import { AbstractBaseAdapter, applyRLS, DEFAULT_RLS_CONFIG, canAccessDocument } from './chunk-DYTZ6FQ7.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);
@@ -721,6 +725,7 @@ function globalToZod(global) {
721
725
 
722
726
  // src/registry/index.ts
723
727
  var Registry = class {
728
+ storageProviders = getDefaultRegistry();
724
729
  collections = /* @__PURE__ */ new Map();
725
730
  globals = /* @__PURE__ */ new Map();
726
731
  plugins = [];
@@ -788,6 +793,19 @@ var Registry = class {
788
793
  if (this.initialized) {
789
794
  throw new Error("Cannot add globals after Registry has been initialized");
790
795
  }
796
+ this._addGlobalUnsafe(config);
797
+ }
798
+ /**
799
+ * Add a global after the registry is already initialized.
800
+ * Only for internal use (e.g. storage settings form built at startup).
801
+ */
802
+ addGlobalPostInit(config) {
803
+ if (this.globals.has(config.slug)) {
804
+ this.globals.delete(config.slug);
805
+ }
806
+ this._addGlobalUnsafe(config);
807
+ }
808
+ _addGlobalUnsafe(config) {
791
809
  if (this.globals.has(config.slug)) {
792
810
  console.warn(
793
811
  `[Registry] Duplicate global slug "${config.slug}" \u2014 skipping`
@@ -942,6 +960,17 @@ var Registry = class {
942
960
  admin: { readOnly: true, hidden: true }
943
961
  });
944
962
  }
963
+ if (config.versions?.drafts && !fields.some((f) => f.name === "publishStatus")) {
964
+ fields.push({
965
+ name: "publishStatus",
966
+ type: "select",
967
+ options: [
968
+ { value: "draft", label: "Draft" },
969
+ { value: "published", label: "Published" }
970
+ ],
971
+ admin: { readOnly: true, hidden: true }
972
+ });
973
+ }
945
974
  if (config.auth && !fields.some((f) => f.name === "password")) {
946
975
  fields.push({
947
976
  name: "password",
@@ -1086,6 +1115,296 @@ function createRegistry() {
1086
1115
  instance = new Registry();
1087
1116
  return instance;
1088
1117
  }
1118
+
1119
+ // src/plugins/storage-s3.ts
1120
+ var s3Variants = {
1121
+ aws: {
1122
+ type: "aws",
1123
+ configKey: "s3",
1124
+ displayName: "S3 Compatible (AWS, Backblaze, Wasabi, etc.)",
1125
+ configFields: [
1126
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1127
+ { name: "region", type: "text", label: "Region", defaultValue: "us-east-1", admin: { placeholder: "us-east-1" } },
1128
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1129
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1130
+ { name: "endpoint", type: "text", label: "Endpoint URL", admin: { placeholder: "https://s3.custom.com" } },
1131
+ { name: "cdnUrl", type: "text", label: "CDN URL", admin: { placeholder: "https://cdn.example.com" } },
1132
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads" } }
1133
+ ]
1134
+ },
1135
+ r2: {
1136
+ type: "r2",
1137
+ configKey: "r2",
1138
+ displayName: "Cloudflare R2",
1139
+ configFields: [
1140
+ { name: "accountId", type: "text", label: "Account ID", required: true, admin: { placeholder: "Your Cloudflare Account ID" } },
1141
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1142
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1143
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1144
+ {
1145
+ name: "publicDevUrl",
1146
+ type: "text",
1147
+ label: "Public Dev URL ID",
1148
+ admin: {
1149
+ placeholder: "pub-xxxxxxxxxxxxxxxx",
1150
+ 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."
1151
+ }
1152
+ },
1153
+ { name: "cdnUrl", type: "text", label: "Custom CDN URL", admin: { placeholder: "https://assets.example.com (optional)" } },
1154
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads (optional)", description: "Optional prefix for all object keys. Do not use '/' as prefix." } }
1155
+ ]
1156
+ },
1157
+ gcs: {
1158
+ type: "gcs",
1159
+ configKey: "gcs",
1160
+ displayName: "Google Cloud Storage",
1161
+ configFields: [
1162
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1163
+ { name: "projectId", type: "text", label: "Project ID" },
1164
+ { name: "clientEmail", type: "text", label: "Client Email" },
1165
+ { name: "privateKey", type: "password", label: "Private Key" },
1166
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1167
+ { name: "prefix", type: "text", label: "Path Prefix" }
1168
+ ]
1169
+ },
1170
+ digitalocean: {
1171
+ type: "digitalocean",
1172
+ configKey: "digitalocean",
1173
+ displayName: "DigitalOcean Spaces",
1174
+ configFields: [
1175
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1176
+ { name: "region", type: "text", label: "Region", defaultValue: "nyc3" },
1177
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1178
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1179
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1180
+ { name: "prefix", type: "text", label: "Path Prefix" }
1181
+ ]
1182
+ },
1183
+ backblaze: {
1184
+ type: "backblaze",
1185
+ configKey: "backblaze",
1186
+ displayName: "Backblaze B2",
1187
+ configFields: [
1188
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1189
+ { name: "accountId", type: "text", label: "Account ID" },
1190
+ { name: "applicationKeyId", type: "text", label: "Application Key ID", required: true },
1191
+ { name: "applicationKey", type: "password", label: "Application Key", required: true },
1192
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1193
+ { name: "prefix", type: "text", label: "Path Prefix" }
1194
+ ]
1195
+ },
1196
+ wasabi: {
1197
+ type: "wasabi",
1198
+ configKey: "wasabi",
1199
+ displayName: "Wasabi",
1200
+ configFields: [
1201
+ { name: "bucket", type: "text", label: "Bucket Name", required: true },
1202
+ { name: "region", type: "text", label: "Region", defaultValue: "us-east-1" },
1203
+ { name: "accessKeyId", type: "text", label: "Access Key ID", required: true },
1204
+ { name: "secretAccessKey", type: "password", label: "Secret Access Key", required: true },
1205
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
1206
+ { name: "prefix", type: "text", label: "Path Prefix" }
1207
+ ]
1208
+ }
1209
+ };
1210
+ function getEndpoint(type, config) {
1211
+ switch (type) {
1212
+ case "r2":
1213
+ return config?.endpoint || `https://${config?.accountId || ""}.r2.cloudflarestorage.com`;
1214
+ case "digitalocean":
1215
+ return config?.endpoint || `https://${config?.region || "nyc3"}.digitaloceanspaces.com`;
1216
+ case "backblaze":
1217
+ return config?.endpoint || `https://s3.backblazeb2.com`;
1218
+ case "wasabi":
1219
+ return config?.endpoint || `https://s3.${config?.region || "us-east-1"}.wasabisys.com`;
1220
+ default:
1221
+ return config?.endpoint;
1222
+ }
1223
+ }
1224
+ function buildS3Config(type, c) {
1225
+ return {
1226
+ provider: type,
1227
+ bucket: c?.bucket || "",
1228
+ region: c?.region || "us-east-1",
1229
+ accessKeyId: c?.accessKeyId || c?.clientEmail || c?.applicationKeyId || "",
1230
+ secretAccessKey: c?.secretAccessKey || c?.privateKey || c?.applicationKey || "",
1231
+ endpoint: getEndpoint(type, c),
1232
+ cdnUrl: c?.cdnUrl,
1233
+ prefix: c?.prefix,
1234
+ accountId: c?.accountId,
1235
+ publicDevUrl: c?.publicDevUrl
1236
+ };
1237
+ }
1238
+ function buildS3ConfigFromStorageConfig(type, def, sc) {
1239
+ const c = sc[def.configKey] || {};
1240
+ return buildS3Config(type, c);
1241
+ }
1242
+ function buildS3ConfigFromRaw(type, def, raw) {
1243
+ const c = raw?.[def.configKey] || raw;
1244
+ return buildS3Config(type, c);
1245
+ }
1246
+ var s3StoragePlugin = {
1247
+ name: "@kyro-cms/storage-s3",
1248
+ version: "1.0.0",
1249
+ description: "S3-compatible storage (AWS R2 GCS DigitalOcean Backblaze Wasabi)",
1250
+ init: (kyro) => {
1251
+ const registry = kyro.storageProviders;
1252
+ if (!registry) return;
1253
+ const pluginName = "@kyro-cms/storage-s3";
1254
+ for (const v of Object.values(s3Variants)) {
1255
+ registry.register({
1256
+ type: v.type,
1257
+ displayName: v.displayName,
1258
+ pluginName,
1259
+ configKey: v.configKey,
1260
+ configFields: v.configFields,
1261
+ extractConfig: (sc) => buildS3ConfigFromStorageConfig(v.type, v, sc),
1262
+ extractRawConfig: (raw) => buildS3ConfigFromRaw(v.type, v, raw),
1263
+ factory: (c) => createS3Storage(c)
1264
+ });
1265
+ }
1266
+ }
1267
+ };
1268
+
1269
+ // src/plugins/storage-cloudinary.ts
1270
+ var cloudinaryStoragePlugin = {
1271
+ name: "@kyro-cms/storage-cloudinary",
1272
+ version: "1.0.0",
1273
+ description: "Cloudinary image and video storage",
1274
+ init: (kyro) => {
1275
+ const registry = kyro.storageProviders;
1276
+ if (!registry) return;
1277
+ registry.register({
1278
+ type: "cloudinary",
1279
+ displayName: "Cloudinary",
1280
+ pluginName: "@kyro-cms/storage-cloudinary",
1281
+ configKey: "cloudinary",
1282
+ configFields: [
1283
+ { name: "cloudName", type: "text", label: "Cloud Name", required: true },
1284
+ { name: "apiKey", type: "text", label: "API Key", required: true },
1285
+ { name: "apiSecret", type: "password", label: "API Secret", required: true },
1286
+ { name: "folder", type: "text", label: "Folder", admin: { placeholder: "Optional folder path" } },
1287
+ {
1288
+ name: "uploadPreset",
1289
+ type: "text",
1290
+ label: "Upload Preset (optional)",
1291
+ admin: { placeholder: "Leave empty for signed uploads", description: "If not set, uploads will be signed with API Secret" }
1292
+ }
1293
+ ],
1294
+ extractConfig: (sc) => ({
1295
+ cloudName: sc.cloudinary?.cloudName || "",
1296
+ apiKey: sc.cloudinary?.apiKey || "",
1297
+ apiSecret: sc.cloudinary?.apiSecret || "",
1298
+ folder: sc.cloudinary?.folder,
1299
+ uploadPreset: sc.cloudinary?.uploadPreset
1300
+ }),
1301
+ extractRawConfig: (c) => {
1302
+ const cl = c?.cloudinary || c;
1303
+ return {
1304
+ cloudName: cl?.cloudName || "",
1305
+ apiKey: cl?.apiKey || "",
1306
+ apiSecret: cl?.apiSecret || "",
1307
+ folder: cl?.folder,
1308
+ uploadPreset: cl?.uploadPreset
1309
+ };
1310
+ },
1311
+ factory: (c) => createCloudinaryStorage(c)
1312
+ });
1313
+ }
1314
+ };
1315
+
1316
+ // src/plugins/storage-ftp.ts
1317
+ var ftpStoragePlugin = {
1318
+ name: "@kyro-cms/storage-ftp",
1319
+ version: "1.0.0",
1320
+ description: "FTP/SFTP storage provider",
1321
+ init: (kyro) => {
1322
+ const registry = kyro.storageProviders;
1323
+ if (!registry) return;
1324
+ registry.register({
1325
+ type: "ftp",
1326
+ displayName: "FTP",
1327
+ pluginName: "@kyro-cms/storage-ftp",
1328
+ configKey: "ftp",
1329
+ configFields: [
1330
+ { name: "host", type: "text", label: "Host", required: true, admin: { placeholder: "ftp.example.com" } },
1331
+ { name: "port", type: "number", label: "Port", defaultValue: 21, admin: { placeholder: "21 for FTP" } },
1332
+ { name: "user", type: "text", label: "Username", required: true },
1333
+ { name: "password", type: "password", label: "Password", required: true },
1334
+ { name: "secure", type: "checkbox", label: "Use TLS/SSL", defaultValue: false, admin: { description: "Enable TLS/SSL for secure connections (FTP only)" } },
1335
+ { name: "baseUrl", type: "text", label: "Base URL", required: true, admin: { placeholder: "https://files.example.com" } },
1336
+ { name: "prefix", type: "text", label: "Path Prefix", admin: { placeholder: "uploads" } }
1337
+ ],
1338
+ extractConfig: (sc) => ({
1339
+ host: sc.ftp?.host || "",
1340
+ port: sc.ftp?.port || 21,
1341
+ user: sc.ftp?.user || "",
1342
+ password: sc.ftp?.password || "",
1343
+ secure: sc.ftp?.secure || false,
1344
+ baseUrl: sc.ftp?.baseUrl || "",
1345
+ prefix: sc.ftp?.prefix,
1346
+ type: "ftp"
1347
+ }),
1348
+ extractRawConfig: (c) => {
1349
+ const ftp = c?.ftp || c;
1350
+ return {
1351
+ host: ftp?.host || "",
1352
+ port: ftp?.port || 21,
1353
+ user: ftp?.user || "",
1354
+ password: ftp?.password || "",
1355
+ secure: ftp?.secure || false,
1356
+ baseUrl: ftp?.baseUrl || "",
1357
+ prefix: ftp?.prefix,
1358
+ type: "ftp"
1359
+ };
1360
+ },
1361
+ factory: (c) => createFtpStorage(c)
1362
+ });
1363
+ }
1364
+ };
1365
+ var builtinStoragePlugins = [
1366
+ s3StoragePlugin,
1367
+ cloudinaryStoragePlugin,
1368
+ ftpStoragePlugin
1369
+ ];
1370
+ function updateFieldByPath(fields, path, updates) {
1371
+ const parts = path.split(".");
1372
+ if (parts.length === 0) return false;
1373
+ const currentPart = parts[0];
1374
+ const remainingPath = parts.slice(1).join(".");
1375
+ for (const field of fields) {
1376
+ if (field.name === currentPart) {
1377
+ if (remainingPath) {
1378
+ if (field.fields && Array.isArray(field.fields)) {
1379
+ return updateFieldByPath(field.fields, remainingPath, updates);
1380
+ }
1381
+ if (field.type === "array" && field.fields && Array.isArray(field.fields)) {
1382
+ return updateFieldByPath(field.fields, remainingPath, updates);
1383
+ }
1384
+ return false;
1385
+ } else {
1386
+ Object.assign(field, updates);
1387
+ return true;
1388
+ }
1389
+ }
1390
+ }
1391
+ return false;
1392
+ }
1393
+ function applyCollectionOverrides(collections, overrides) {
1394
+ if (!overrides) return;
1395
+ for (const col of collections) {
1396
+ const override = overrides[col.slug];
1397
+ if (override) {
1398
+ const { fields: fieldOverrides, ...adminOverrides } = override;
1399
+ col.admin = { ...col.admin, ...adminOverrides };
1400
+ if (fieldOverrides && col.fields && Array.isArray(col.fields)) {
1401
+ for (const [fieldPath, fieldUpdates] of Object.entries(fieldOverrides)) {
1402
+ updateFieldByPath(col.fields, fieldPath, fieldUpdates);
1403
+ }
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1089
1408
  var Kyro = class {
1090
1409
  registry;
1091
1410
  db;
@@ -1100,12 +1419,18 @@ var Kyro = class {
1100
1419
  this.db = config.adapter;
1101
1420
  this.pubsub = new KyroPubSub(this.registry);
1102
1421
  this.webhookService = createWebhookService(this.db);
1422
+ if (config.collections && config.admin?.collectionOverrides) {
1423
+ applyCollectionOverrides(config.collections, config.admin.collectionOverrides);
1424
+ }
1103
1425
  if (config.collections) {
1104
1426
  this.registry.addCollections(config.collections);
1105
1427
  }
1106
1428
  if (config.globals) {
1107
1429
  this.registry.addGlobals(config.globals);
1108
1430
  }
1431
+ for (const plugin of builtinStoragePlugins) {
1432
+ this.registry.addPlugin(plugin);
1433
+ }
1109
1434
  if (config.plugins) {
1110
1435
  for (const plugin of config.plugins) {
1111
1436
  this.registry.addPlugin(plugin);
@@ -1114,6 +1439,17 @@ var Kyro = class {
1114
1439
  }
1115
1440
  async init() {
1116
1441
  await this.registry.init();
1442
+ const storageGlobal = createStorageSettingsGlobal(
1443
+ this.registry.storageProviders,
1444
+ (name) => this.registry.storageProviders.isPluginEnabled(name)
1445
+ );
1446
+ this.registry.addGlobalPostInit(storageGlobal);
1447
+ const pluginSettingsGlobal = {
1448
+ slug: "plugin-settings",
1449
+ admin: { hidden: true },
1450
+ fields: [{ name: "states", type: "json" }]
1451
+ };
1452
+ this.registry.addGlobalPostInit(pluginSettingsGlobal);
1117
1453
  if (!this.db) {
1118
1454
  throw new Error(
1119
1455
  `Database adapter is null \u2014 failed to load at startup. Check the server console for the exact error.`
@@ -1160,6 +1496,7 @@ var Kyro = class {
1160
1496
  { name: "nextRetryAt", type: "date" }
1161
1497
  ]
1162
1498
  };
1499
+ const allGlobals = this.registry.getGlobals();
1163
1500
  await this.db.init(
1164
1501
  [
1165
1502
  ...this.registry.getCollections(),
@@ -1167,12 +1504,13 @@ var Kyro = class {
1167
1504
  webhookCollection,
1168
1505
  webhookDeliveryCollection
1169
1506
  ],
1170
- this.registry.getGlobals()
1507
+ allGlobals
1171
1508
  );
1509
+ await this.loadPluginState();
1172
1510
  this.pubsub.autoRegisterHooks();
1173
1511
  console.log("\u2705 Kyro CMS initialized");
1174
1512
  console.log(` Collections: ${this.registry.getCollections().length}`);
1175
- console.log(` Globals: ${this.registry.getGlobals().length}`);
1513
+ console.log(` Globals: ${allGlobals.length}`);
1176
1514
  }
1177
1515
  // ============================================================================
1178
1516
  // API Methods
@@ -1181,18 +1519,38 @@ var Kyro = class {
1181
1519
  async loadSettings() {
1182
1520
  if (this.settings) return this.settings;
1183
1521
  try {
1184
- const accessSettings = await this.db.findOne({
1185
- collection: "_globals",
1186
- where: { slug: "access-settings" }
1522
+ const doc = await this.db.findOne({
1523
+ collection: "_globals_access-settings",
1524
+ where: {}
1187
1525
  });
1188
- if (accessSettings) {
1189
- this.settings = accessSettings.data;
1526
+ if (doc) {
1527
+ this.settings = { access: doc };
1190
1528
  }
1191
1529
  } catch (e) {
1192
1530
  console.log("\u26A0\uFE0F No access-settings found, using defaults");
1193
1531
  }
1194
1532
  return this.settings || {};
1195
1533
  }
1534
+ async loadPluginState() {
1535
+ const storageRegistry = this.registry.storageProviders;
1536
+ const pluginNames = storageRegistry.getAllPluginNames();
1537
+ let pluginStates = {};
1538
+ try {
1539
+ const doc = await this.db.findOne({
1540
+ collection: "_globals_plugin-settings",
1541
+ where: {}
1542
+ });
1543
+ if (doc && doc.states) {
1544
+ pluginStates = doc.states;
1545
+ }
1546
+ } catch (e) {
1547
+ }
1548
+ for (const name of pluginNames) {
1549
+ if (pluginStates[name] !== void 0) {
1550
+ storageRegistry.setPluginEnabled(name, pluginStates[name]);
1551
+ }
1552
+ }
1553
+ }
1196
1554
  getREST(options) {
1197
1555
  const authObj = typeof this.config.auth === "object" ? this.config.auth : null;
1198
1556
  const authSecret = authObj?.secret;
@@ -1210,33 +1568,196 @@ var Kyro = class {
1210
1568
  });
1211
1569
  }
1212
1570
  getGraphQL(options) {
1213
- return buildGraphQLSchema({
1571
+ const defaultSchema = buildGraphQLSchema({
1214
1572
  registry: this.registry,
1215
1573
  db: this.db,
1216
- ...options,
1217
- settings: this.settings
1574
+ settings: this.settings,
1575
+ user: options?.user,
1576
+ req: options?.req,
1577
+ tenantID: options?.tenantID
1218
1578
  });
1579
+ return {
1580
+ fetch: async (request, _locals) => {
1581
+ const apiKeyRaw = extractApiKeyFromRequest(request);
1582
+ let gqlUser;
1583
+ let apiKeyCtx;
1584
+ if (apiKeyRaw && this.db) {
1585
+ const apiKeyResult = await validateApiKey(apiKeyRaw, this.db);
1586
+ if (!apiKeyResult.valid) {
1587
+ return new Response(
1588
+ JSON.stringify({ errors: [{ message: apiKeyResult.error || "Invalid API key" }] }),
1589
+ { status: 401, headers: { "Content-Type": "application/json" } }
1590
+ );
1591
+ }
1592
+ if (apiKeyResult.user) {
1593
+ gqlUser = apiKeyResult.user;
1594
+ apiKeyCtx = createApiKeyContext(apiKeyResult);
1595
+ }
1596
+ }
1597
+ const mustRebuild = gqlUser !== options?.user || apiKeyCtx !== void 0;
1598
+ const schema = mustRebuild ? buildGraphQLSchema({
1599
+ registry: this.registry,
1600
+ db: this.db,
1601
+ settings: this.settings,
1602
+ user: gqlUser,
1603
+ req: request,
1604
+ tenantID: options?.tenantID,
1605
+ apiKey: apiKeyCtx
1606
+ }) : defaultSchema;
1607
+ const body = request.method === "POST" ? await request.json().catch(() => ({})) : {};
1608
+ const query = body.query || "";
1609
+ const variables = body.variables || {};
1610
+ if (!query) {
1611
+ return new Response(
1612
+ JSON.stringify({ error: "No GraphQL query provided" }),
1613
+ { status: 400, headers: { "Content-Type": "application/json" } }
1614
+ );
1615
+ }
1616
+ try {
1617
+ const document = parse(query);
1618
+ const result = await execute({
1619
+ schema,
1620
+ document,
1621
+ variableValues: variables,
1622
+ contextValue: {
1623
+ db: this.db,
1624
+ registry: this.registry,
1625
+ settings: this.settings,
1626
+ user: gqlUser,
1627
+ req: request,
1628
+ tenantID: options?.tenantID
1629
+ }
1630
+ });
1631
+ return new Response(JSON.stringify(result), {
1632
+ status: 200,
1633
+ headers: { "Content-Type": "application/json" }
1634
+ });
1635
+ } catch (err) {
1636
+ return new Response(
1637
+ JSON.stringify({ errors: [{ message: err.message }] }),
1638
+ { status: 400, headers: { "Content-Type": "application/json" } }
1639
+ );
1640
+ }
1641
+ },
1642
+ schema: defaultSchema
1643
+ };
1219
1644
  }
1220
1645
  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
- });
1646
+ return {
1647
+ fetch: async (request, locals) => {
1648
+ const url = new URL(request.url);
1649
+ const path = url.pathname.replace(/^\/api\/trpc\//, "");
1650
+ const [slug, ...rest] = path.split(".");
1651
+ let procedureName = rest.join(".");
1652
+ procedureName = procedureName.replace(/\.(query|mutate|subscribe)$/, "");
1653
+ if (!slug || !procedureName) {
1654
+ return new Response(
1655
+ JSON.stringify({
1656
+ error: {
1657
+ message: "Invalid tRPC path",
1658
+ code: -32600,
1659
+ data: { code: "BAD_REQUEST", httpStatus: 400 }
1660
+ }
1661
+ }),
1662
+ { status: 400, headers: { "Content-Type": "application/json" } }
1663
+ );
1664
+ }
1665
+ const ctx = await createContext({
1666
+ db: this.db,
1667
+ registry: this.registry,
1668
+ req: request,
1669
+ user: options?.user,
1670
+ tenantID: options?.tenantID,
1671
+ settings: this.settings
1672
+ });
1673
+ const kyroRouter = createKyroServer(ctx);
1674
+ const collectionRouter = kyroRouter[slug];
1675
+ if (!collectionRouter) {
1676
+ return new Response(
1677
+ JSON.stringify({
1678
+ error: {
1679
+ message: `Collection '${slug}' not found`,
1680
+ code: -32601,
1681
+ data: { code: "NOT_FOUND", httpStatus: 404 }
1682
+ }
1683
+ }),
1684
+ { status: 404, headers: { "Content-Type": "application/json" } }
1685
+ );
1686
+ }
1687
+ const procedure = collectionRouter[procedureName];
1688
+ if (typeof procedure !== "function") {
1689
+ return new Response(
1690
+ JSON.stringify({
1691
+ error: {
1692
+ message: `Procedure '${procedureName}' not found`,
1693
+ code: -32601,
1694
+ data: { code: "NOT_FOUND", httpStatus: 404 }
1695
+ }
1696
+ }),
1697
+ { status: 404, headers: { "Content-Type": "application/json" } }
1698
+ );
1699
+ }
1700
+ try {
1701
+ let raw = {};
1702
+ if (request.method === "POST" || request.method === "PATCH") {
1703
+ raw = await request.json().catch(() => ({}));
1704
+ } else {
1705
+ const qs = new URL(request.url).searchParams.get("input");
1706
+ if (qs) {
1707
+ try {
1708
+ raw = JSON.parse(decodeURIComponent(qs));
1709
+ } catch {
1710
+ }
1711
+ }
1712
+ }
1713
+ const input = raw?.["0"] ?? raw;
1714
+ const result = await procedure({ ...input, collection: slug });
1715
+ return new Response(JSON.stringify({ result: { data: result } }), {
1716
+ status: 200,
1717
+ headers: { "Content-Type": "application/json" }
1718
+ });
1719
+ } catch (err) {
1720
+ const msg = err.message || "Internal error";
1721
+ const httpStatus = msg.includes("not found") ? 404 : msg.includes("denied") || msg.includes("authentication required") ? 403 : msg.includes("conflict") ? 409 : 500;
1722
+ const code = httpStatus === 404 ? -32601 : httpStatus === 403 ? -32001 : httpStatus === 409 ? -32002 : -32603;
1723
+ return new Response(
1724
+ JSON.stringify({
1725
+ error: {
1726
+ message: msg,
1727
+ code,
1728
+ data: {
1729
+ code: "INTERNAL_SERVER_ERROR",
1730
+ httpStatus
1731
+ }
1732
+ }
1733
+ }),
1734
+ { status: httpStatus, headers: { "Content-Type": "application/json" } }
1735
+ );
1736
+ }
1737
+ },
1738
+ router: null
1739
+ };
1740
+ }
1741
+ getWS() {
1742
+ return this.wsServer;
1228
1743
  }
1229
1744
  async startWebSocket(options) {
1230
1745
  const apiAccess = this.settings?.access?.apiAccess;
1231
- if (apiAccess?.websocketEnabled === false) {
1746
+ if (apiAccess?.wsEnabled === false) {
1232
1747
  console.log("\u26A0\uFE0F WebSocket is disabled in settings");
1233
1748
  return null;
1234
1749
  }
1750
+ const defaultVerifyToken = async (token) => {
1751
+ const result = await validateApiKey(token, this.db);
1752
+ if (!result.valid) throw new Error(result.error || "Invalid API key");
1753
+ if (!result.user) throw new Error("API key has no associated user");
1754
+ return result.user;
1755
+ };
1235
1756
  this.wsServer = createWSServer({
1236
1757
  pubsub: this.pubsub,
1237
1758
  port: options?.port || 8080,
1238
1759
  requireAuth: options?.requireAuth ?? apiAccess?.requireAuth,
1239
- verifyToken: options?.verifyToken
1760
+ verifyToken: options?.verifyToken || defaultVerifyToken
1240
1761
  });
1241
1762
  console.log(`\u{1F50C} WebSocket server started on port ${options?.port || 8080}`);
1242
1763
  return this.wsServer;
@@ -1361,13 +1882,6 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1361
1882
  migrations = /* @__PURE__ */ new Map();
1362
1883
  draftsTableName = "kyro_drafts";
1363
1884
  versionsTableName = "kyro_versions";
1364
- tenantContext;
1365
- setTenantContext(context) {
1366
- this.tenantContext = context;
1367
- }
1368
- getTenantContext() {
1369
- return this.tenantContext;
1370
- }
1371
1885
  constructor(options) {
1372
1886
  super();
1373
1887
  this.path = options.path;
@@ -1381,6 +1895,34 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1381
1895
  if (!this.db) {
1382
1896
  this.db = new DatabaseSync(this.path || ":memory:");
1383
1897
  }
1898
+ if (this.db && typeof this.db.prepare === "function" && !this.db.prepare.__wrapped) {
1899
+ const originalPrepare = this.db.prepare.bind(this.db);
1900
+ const wrappedPrepare = (sql) => {
1901
+ const stmt = originalPrepare(sql);
1902
+ const serialize = (val) => {
1903
+ if (typeof val === "boolean") return val ? 1 : 0;
1904
+ if (val === void 0) return null;
1905
+ return val;
1906
+ };
1907
+ return new Proxy(stmt, {
1908
+ get(target, prop, receiver) {
1909
+ if (prop === "all") {
1910
+ return (...params) => target.all(...params.map(serialize));
1911
+ }
1912
+ if (prop === "get") {
1913
+ return (...params) => target.get(...params.map(serialize));
1914
+ }
1915
+ if (prop === "run") {
1916
+ return (...params) => target.run(...params.map(serialize));
1917
+ }
1918
+ const val = Reflect.get(target, prop, receiver);
1919
+ return typeof val === "function" ? val.bind(target) : val;
1920
+ }
1921
+ });
1922
+ };
1923
+ wrappedPrepare.__wrapped = true;
1924
+ this.db.prepare = wrappedPrepare;
1925
+ }
1384
1926
  this.db.exec("PRAGMA journal_mode = WAL");
1385
1927
  this.db.exec("PRAGMA foreign_keys = ON");
1386
1928
  this.connected = true;
@@ -1408,8 +1950,8 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1408
1950
  }
1409
1951
  columns.push(`${this.col("createdAt")} TEXT DEFAULT (datetime('now'))`);
1410
1952
  columns.push(`${this.col("updatedAt")} TEXT DEFAULT (datetime('now'))`);
1411
- columns.push(`_status TEXT DEFAULT 'published'`);
1412
- columns.push(`_has_draft INTEGER DEFAULT 0`);
1953
+ columns.push(`publishStatus TEXT DEFAULT 'draft'`);
1954
+ columns.push(`hasDraft INTEGER DEFAULT 0`);
1413
1955
  if (config.tenantScoped) {
1414
1956
  columns.push(`tenant_id TEXT NOT NULL`);
1415
1957
  }
@@ -1417,7 +1959,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1417
1959
  if (existingColumns.length === 0) {
1418
1960
  const createSQL = `CREATE TABLE IF NOT EXISTS ${name} (${columns.join(", ")})`;
1419
1961
  this.db.exec(createSQL);
1420
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${name}__status ON ${name}(_status)`);
1962
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${name}_publishStatus ON ${name}(publishStatus)`);
1421
1963
  for (const field of flattenFields(config.fields)) {
1422
1964
  if (field.name && field.indexed) {
1423
1965
  this.db.exec(
@@ -1436,9 +1978,9 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1436
1978
  const colName = colDef.split(" ")[0].replace(/^"/, "").replace(/"$/, "");
1437
1979
  if (!existingSet.has(colName) && colName !== "id") {
1438
1980
  try {
1439
- if (colName === "_status") {
1981
+ if (colName === "publishStatus") {
1440
1982
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} TEXT DEFAULT 'published'`);
1441
- } else if (colName === "_has_draft") {
1983
+ } else if (colName === "hasDraft") {
1442
1984
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} INTEGER DEFAULT 0`);
1443
1985
  } else {
1444
1986
  this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} TEXT`);
@@ -1471,7 +2013,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1471
2013
  `CREATE INDEX IF NOT EXISTS idx_${this.versionsTableName}_doc ON ${this.versionsTableName}(collection_slug, document_id)`
1472
2014
  );
1473
2015
  this.db.exec(
1474
- `CREATE INDEX IF NOT EXISTS idx_${this.versionsTableName}_status ON ${this.versionsTableName}(status)`
2016
+ `CREATE INDEX IF NOT EXISTS idx_${this.versionsTableName}publishStatus ON ${this.versionsTableName}(status)`
1475
2017
  );
1476
2018
  }
1477
2019
  ensureDraftsTable() {
@@ -1572,7 +2114,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1572
2114
  effectiveWhere = rlsQuery.where || {};
1573
2115
  }
1574
2116
  if (!draft && config.versions?.drafts) {
1575
- conditions.push(`_status = ?`);
2117
+ conditions.push(`publishStatus = ?`);
1576
2118
  params.push("published");
1577
2119
  }
1578
2120
  if (tenantID && config.tenantScoped) {
@@ -1617,7 +2159,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1617
2159
  }
1618
2160
  if (draft) {
1619
2161
  docs = await Promise.all(docs.map(async (doc) => {
1620
- if (doc._has_draft) {
2162
+ if (doc.hasDraft) {
1621
2163
  const versions = await this.findVersions({
1622
2164
  collection: slug,
1623
2165
  documentId: doc.id,
@@ -1625,7 +2167,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1625
2167
  sort: "-createdAt"
1626
2168
  });
1627
2169
  if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1628
- return { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
2170
+ return { ...doc, ...versions.docs[0].data, hasDraft: true, publishStatus: doc.publishStatus };
1629
2171
  }
1630
2172
  }
1631
2173
  return doc;
@@ -1659,7 +2201,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1659
2201
  }
1660
2202
  }
1661
2203
  if (!draft && config.versions?.drafts) {
1662
- sql += ` AND _status = ?`;
2204
+ sql += ` AND publishStatus = ?`;
1663
2205
  params.push("published");
1664
2206
  }
1665
2207
  if (tenantID && config.tenantScoped) {
@@ -1669,7 +2211,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1669
2211
  const row = this.db.prepare(sql).get(...params);
1670
2212
  if (!row) return null;
1671
2213
  let doc = this.rowToDoc(row, config);
1672
- if (draft && doc._has_draft) {
2214
+ if (draft && doc.hasDraft) {
1673
2215
  const versions = await this.findVersions({
1674
2216
  collection: slug,
1675
2217
  documentId: doc.id,
@@ -1677,7 +2219,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1677
2219
  sort: "-createdAt"
1678
2220
  });
1679
2221
  if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1680
- doc = { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
2222
+ doc = { ...doc, ...versions.docs[0].data, hasDraft: true, publishStatus: doc.publishStatus };
1681
2223
  }
1682
2224
  }
1683
2225
  return doc;
@@ -1708,7 +2250,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1708
2250
  const quotedColumns = filteredColumns.map((c) => this.col(c));
1709
2251
  const placeholders = filteredColumns.map(() => "?").join(", ");
1710
2252
  const values = Object.values(filteredData).map(
1711
- (v) => typeof v === "object" ? JSON.stringify(v) : v
2253
+ (v) => v !== null && typeof v === "object" ? JSON.stringify(v) : v
1712
2254
  );
1713
2255
  this.db.prepare(
1714
2256
  `INSERT OR REPLACE INTO ${tableName} (${quotedColumns.join(", ")}) VALUES (${placeholders})`
@@ -1788,7 +2330,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1788
2330
  const conditions = [];
1789
2331
  const params = [];
1790
2332
  if (!args.draft && globalConfig.versions) {
1791
- conditions.push("_status = 'published'");
2333
+ conditions.push("publishStatus = 'published'");
1792
2334
  }
1793
2335
  if (conditions.length > 0) {
1794
2336
  sql += ` WHERE ${conditions.join(" AND ")}`;
@@ -1797,7 +2339,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1797
2339
  const result2 = this.db.prepare(sql).get(...params);
1798
2340
  if (result2) {
1799
2341
  let doc = this.rowToDoc(result2, globalConfig);
1800
- if (args.draft && doc._has_draft) {
2342
+ if (args.draft && doc.hasDraft) {
1801
2343
  const versions = await this.findVersions({
1802
2344
  collection: args.collection,
1803
2345
  documentId: parsed.globalSlug,
@@ -1805,7 +2347,7 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1805
2347
  sort: "-createdAt"
1806
2348
  });
1807
2349
  if (versions.docs.length > 0 && versions.docs[0].status === "draft") {
1808
- doc = { ...doc, ...versions.docs[0].data, _has_draft: true, _status: doc._status };
2350
+ doc = { ...doc, ...versions.docs[0].data, hasDraft: true, publishStatus: doc.publishStatus };
1809
2351
  }
1810
2352
  }
1811
2353
  return doc;
@@ -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.publishStatus = row.publishStatus ?? "published";
2693
+ doc.hasDraft = row.hasDraft ? Boolean(row.hasDraft) : false;
2150
2694
  return doc;
2151
2695
  }
2152
2696
  generateId() {
@@ -2232,5 +2776,5 @@ function createLocalAdapter(options) {
2232
2776
  }
2233
2777
 
2234
2778
  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
2779
+ //# sourceMappingURL=chunk-22M4O4ZJ.js.map
2780
+ //# sourceMappingURL=chunk-22M4O4ZJ.js.map