@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.
- package/README.md +55 -593
- package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-BKszZlG0.d.cts} +1 -1
- package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-Ccf1j-IN.d.ts} +1 -1
- package/dist/api-handler-graphql.cjs +44 -0
- package/dist/api-handler-graphql.cjs.map +1 -0
- package/dist/api-handler-graphql.d.cts +6 -0
- package/dist/api-handler-graphql.d.ts +6 -0
- package/dist/api-handler-graphql.js +41 -0
- package/dist/api-handler-graphql.js.map +1 -0
- package/dist/api-handler-trpc.cjs +38 -0
- package/dist/api-handler-trpc.cjs.map +1 -0
- package/dist/api-handler-trpc.d.cts +5 -0
- package/dist/api-handler-trpc.d.ts +5 -0
- package/dist/api-handler-trpc.js +36 -0
- package/dist/api-handler-trpc.js.map +1 -0
- package/dist/api-handler.cjs +33 -99
- package/dist/api-handler.cjs.map +1 -1
- package/dist/api-handler.d.cts +2 -1
- package/dist/api-handler.d.ts +2 -1
- package/dist/api-handler.js +21 -97
- package/dist/api-handler.js.map +1 -1
- package/dist/{tenant-B1YB0Jy8.d.ts → base-CIuXkrH4.d.cts} +7 -15
- package/dist/{tenant-Cpeveji6.d.cts → base-fFo4lqER.d.ts} +7 -15
- package/dist/bootstrap-3PV3GJ3S.js +7 -0
- package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-3PV3GJ3S.js.map} +1 -1
- package/dist/bootstrap-4CELFLJO.cjs +32 -0
- package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-4CELFLJO.cjs.map} +1 -1
- package/dist/{chunk-VJT6P4N6.cjs → chunk-3HR772HI.cjs} +199 -32
- package/dist/chunk-3HR772HI.cjs.map +1 -0
- package/dist/chunk-3KTWGODI.cjs +178 -0
- package/dist/chunk-3KTWGODI.cjs.map +1 -0
- package/dist/{chunk-QXIQWPAP.js → chunk-3UK5XBVJ.js} +4 -134
- package/dist/chunk-3UK5XBVJ.js.map +1 -0
- package/dist/{chunk-FXYP2HA6.js → chunk-4AO3A3JM.js} +48 -4
- package/dist/chunk-4AO3A3JM.js.map +1 -0
- package/dist/{chunk-Z6ZWNWWR.js → chunk-4CV4JOE5.js} +3 -9
- package/dist/{chunk-Z6ZWNWWR.js.map → chunk-4CV4JOE5.js.map} +1 -1
- package/dist/chunk-4M7X5HAB.cjs +173 -0
- package/dist/chunk-4M7X5HAB.cjs.map +1 -0
- package/dist/chunk-53NYVYVX.js +3243 -0
- package/dist/chunk-53NYVYVX.js.map +1 -0
- package/dist/{chunk-35U3FROB.js → chunk-5H3MWQJS.js} +714 -184
- package/dist/chunk-5H3MWQJS.js.map +1 -0
- package/dist/{chunk-YVUJBEXE.cjs → chunk-5PMQQFRE.cjs} +16 -7
- package/dist/chunk-5PMQQFRE.cjs.map +1 -0
- package/dist/{chunk-57P6MJKC.js → chunk-6UNONDW7.js} +94 -10
- package/dist/chunk-6UNONDW7.js.map +1 -0
- package/dist/{chunk-Y3N7UUDO.js → chunk-7OGPN7MP.js} +5 -2
- package/dist/chunk-7OGPN7MP.js.map +1 -0
- package/dist/{chunk-2OL4O2TH.cjs → chunk-7OS7TX2Q.cjs} +68 -62
- package/dist/chunk-7OS7TX2Q.cjs.map +1 -0
- package/dist/{chunk-3TPQ2BU6.js → chunk-BYBMTIMT.js} +2 -6
- package/dist/chunk-BYBMTIMT.js.map +1 -0
- package/dist/{chunk-ES5HNFFT.js → chunk-CF7OL6HR.js} +4 -2
- package/dist/chunk-CF7OL6HR.js.map +1 -0
- package/dist/chunk-CJONKRHJ.js +162 -0
- package/dist/chunk-CJONKRHJ.js.map +1 -0
- package/dist/{chunk-OHVB4AJ7.js → chunk-CJX74IYK.js} +24 -18
- package/dist/chunk-CJX74IYK.js.map +1 -0
- package/dist/{chunk-5KVM3WEY.cjs → chunk-CNKT4PME.cjs} +1592 -868
- package/dist/chunk-CNKT4PME.cjs.map +1 -0
- package/dist/{chunk-G7VZBCD6.cjs → chunk-CZLDE2OZ.cjs} +2 -9
- package/dist/{chunk-G7VZBCD6.cjs.map → chunk-CZLDE2OZ.cjs.map} +1 -1
- package/dist/{chunk-WQBRWOQT.cjs → chunk-DPA3KWPY.cjs} +4 -3
- package/dist/chunk-DPA3KWPY.cjs.map +1 -0
- package/dist/{chunk-LINKCEG4.cjs → chunk-E2763JUP.cjs} +726 -196
- package/dist/chunk-E2763JUP.cjs.map +1 -0
- package/dist/chunk-E5UJBLQ7.js +220 -0
- package/dist/chunk-E5UJBLQ7.js.map +1 -0
- package/dist/{chunk-DVD5P72E.cjs → chunk-EEJUFDMF.cjs} +2 -6
- package/dist/chunk-EEJUFDMF.cjs.map +1 -0
- package/dist/chunk-FSKONGCX.cjs +253 -0
- package/dist/chunk-FSKONGCX.cjs.map +1 -0
- package/dist/{chunk-Y3QQN7PN.js → chunk-GAAHG2Z4.js} +13 -4
- package/dist/chunk-GAAHG2Z4.js.map +1 -0
- package/dist/chunk-GAOXD3XT.js +175 -0
- package/dist/chunk-GAOXD3XT.js.map +1 -0
- package/dist/{chunk-SA7NSSIQ.cjs → chunk-GUUB5EAG.cjs} +13 -187
- package/dist/chunk-GUUB5EAG.cjs.map +1 -0
- package/dist/{chunk-4DA7QPLA.cjs → chunk-GXFOGU7N.cjs} +5 -2
- package/dist/chunk-GXFOGU7N.cjs.map +1 -0
- package/dist/{chunk-I7HHI6QV.cjs → chunk-IDVRRRAK.cjs} +17 -9
- package/dist/chunk-IDVRRRAK.cjs.map +1 -0
- package/dist/{chunk-HXRD4B37.js → chunk-IPTZM3VE.js} +1423 -704
- package/dist/chunk-IPTZM3VE.js.map +1 -0
- package/dist/chunk-KC2GDBLS.cjs +84 -0
- package/dist/chunk-KC2GDBLS.cjs.map +1 -0
- package/dist/{chunk-QUW2RZTM.cjs → chunk-L46ROHUS.cjs} +51 -7
- package/dist/chunk-L46ROHUS.cjs.map +1 -0
- package/dist/chunk-L4EZKIEX.js +185 -0
- package/dist/chunk-L4EZKIEX.js.map +1 -0
- package/dist/{chunk-REK7AYOC.js → chunk-L5UKKZQN.js} +199 -32
- package/dist/chunk-L5UKKZQN.js.map +1 -0
- package/dist/chunk-NKPKR5BW.cjs +188 -0
- package/dist/chunk-NKPKR5BW.cjs.map +1 -0
- package/dist/chunk-NWUEVLQT.cjs +99 -0
- package/dist/chunk-NWUEVLQT.cjs.map +1 -0
- package/dist/{chunk-3AJE4SEG.js → chunk-OHC6UHFY.js} +208 -76
- package/dist/chunk-OHC6UHFY.js.map +1 -0
- package/dist/chunk-PHJRNPHY.cjs +3291 -0
- package/dist/chunk-PHJRNPHY.cjs.map +1 -0
- package/dist/{chunk-DXHRBMGB.js → chunk-PQ72Z6WC.js} +67 -112
- package/dist/chunk-PQ72Z6WC.js.map +1 -0
- package/dist/{chunk-K7JPTH3G.cjs → chunk-PV2I2KMI.cjs} +214 -82
- package/dist/chunk-PV2I2KMI.cjs.map +1 -0
- package/dist/{chunk-PDYFVNUX.cjs → chunk-Q23GAMLE.cjs} +71 -116
- package/dist/chunk-Q23GAMLE.cjs.map +1 -0
- package/dist/{chunk-H727JIG7.js → chunk-Q72BOAPK.js} +16 -8
- package/dist/chunk-Q72BOAPK.js.map +1 -0
- package/dist/{chunk-IBG6V56E.cjs → chunk-QFLB4EIJ.cjs} +2 -139
- package/dist/chunk-QFLB4EIJ.cjs.map +1 -0
- package/dist/{chunk-2KVHZE6O.cjs → chunk-RFFSZSCL.cjs} +282 -190
- package/dist/chunk-RFFSZSCL.cjs.map +1 -0
- package/dist/{chunk-V3LKPM3O.cjs → chunk-SHTTJMLT.cjs} +4 -2
- package/dist/chunk-SHTTJMLT.cjs.map +1 -0
- package/dist/{chunk-WOWUL7ZY.js → chunk-UUDTPZX6.js} +5 -4
- package/dist/chunk-UUDTPZX6.js.map +1 -0
- package/dist/{chunk-QPPDLRNR.js → chunk-V7KZQIZ6.js} +277 -185
- package/dist/chunk-V7KZQIZ6.js.map +1 -0
- package/dist/{chunk-3ZFYL34R.js → chunk-WXVB364T.js} +12 -185
- package/dist/chunk-WXVB364T.js.map +1 -0
- package/dist/chunk-XEB7PH2E.js +81 -0
- package/dist/chunk-XEB7PH2E.js.map +1 -0
- package/dist/{chunk-IA6AU5PI.cjs → chunk-Y7AQK4R4.cjs} +94 -10
- package/dist/chunk-Y7AQK4R4.cjs.map +1 -0
- package/dist/chunk-YFAVQQTU.js +92 -0
- package/dist/chunk-YFAVQQTU.js.map +1 -0
- package/dist/cli/index.cjs +6 -6
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/client.cjs +4 -4
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +2 -2
- package/dist/drizzle/index.cjs +15 -14
- package/dist/drizzle/index.d.cts +10 -14
- package/dist/drizzle/index.d.ts +10 -14
- package/dist/drizzle/index.js +6 -5
- package/dist/fields/index.cjs +22 -38
- package/dist/fields/index.d.cts +2 -22
- package/dist/fields/index.d.ts +2 -22
- package/dist/fields/index.js +2 -2
- package/dist/graphql/index.cjs +6 -5
- package/dist/graphql/index.d.cts +5 -3
- package/dist/graphql/index.d.ts +5 -3
- package/dist/graphql/index.js +4 -3
- package/dist/index-BKta3cBH.d.cts +277 -0
- package/dist/index-ClOqnkTO.d.ts +277 -0
- package/dist/index.cjs +310 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -211
- package/dist/index.d.ts +130 -211
- package/dist/index.js +174 -35
- package/dist/index.js.map +1 -1
- package/dist/integration.cjs +3 -3
- package/dist/integration.js +2 -2
- package/dist/media-7WDX4BDJ.js +4 -0
- package/dist/{media-GPPTZ43E.js.map → media-7WDX4BDJ.js.map} +1 -1
- package/dist/{media-XNTUFJZR.cjs → media-TUSLVRQ6.cjs} +3 -3
- package/dist/{media-XNTUFJZR.cjs.map → media-TUSLVRQ6.cjs.map} +1 -1
- package/dist/mongo-auth-adapter-GT4S7SCU.cjs +17 -0
- package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-GT4S7SCU.cjs.map} +1 -1
- package/dist/mongo-auth-adapter-M7VV4LNB.js +4 -0
- package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-M7VV4LNB.js.map} +1 -1
- package/dist/mongodb/index.cjs +9 -8
- package/dist/mongodb/index.d.cts +6 -13
- package/dist/mongodb/index.d.ts +6 -13
- package/dist/mongodb/index.js +5 -4
- package/dist/postgres-auth-adapter-AFAPISH7.js +5 -0
- package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-AFAPISH7.js.map} +1 -1
- package/dist/postgres-auth-adapter-SFDTLONT.cjs +14 -0
- package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-SFDTLONT.cjs.map} +1 -1
- package/dist/redis-adapter-UQX4EE3B.cjs +13 -0
- package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-UQX4EE3B.cjs.map} +1 -1
- package/dist/redis-adapter-XALOGWY3.js +4 -0
- package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-XALOGWY3.js.map} +1 -1
- package/dist/rest/index.cjs +16 -15
- package/dist/rest/index.d.cts +4 -4
- package/dist/rest/index.d.ts +4 -4
- package/dist/rest/index.js +14 -13
- package/dist/{schema-37SE2F4B.cjs → schema-6QL3USNB.cjs} +15 -15
- package/dist/{schema-37SE2F4B.cjs.map → schema-6QL3USNB.cjs.map} +1 -1
- package/dist/{schema-5PHL5IVB.js → schema-FNNWEAAW.js} +4 -4
- package/dist/{schema-5PHL5IVB.js.map → schema-FNNWEAAW.js.map} +1 -1
- package/dist/sqlite-adapter-AQB5TCGV.cjs +13 -0
- package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-AQB5TCGV.cjs.map} +1 -1
- package/dist/sqlite-adapter-N5H6IM2X.js +4 -0
- package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-N5H6IM2X.js.map} +1 -1
- package/dist/templates/index.cjs +134 -32
- package/dist/templates/index.d.cts +52 -9
- package/dist/templates/index.d.ts +52 -9
- package/dist/templates/index.js +4 -2
- package/dist/trpc/index.cjs +14 -13
- package/dist/trpc/index.d.cts +55 -49
- package/dist/trpc/index.d.ts +55 -49
- package/dist/trpc/index.js +5 -4
- package/dist/{types-D6ZLRGbH.d.cts → types-CpjuXbe7.d.cts} +2 -0
- package/dist/{types-D6ZLRGbH.d.ts → types-CpjuXbe7.d.ts} +2 -0
- package/dist/{types-VtjUxIMp.d.cts → types-DeSApf9T.d.cts} +36 -14
- package/dist/{types-VtjUxIMp.d.ts → types-DeSApf9T.d.ts} +36 -14
- package/dist/{types-J3R9nVsZ.d.cts → types-Dgzlftb7.d.ts} +32 -28
- package/dist/{types-Bs1up4yP.d.ts → types-Ds0tCA3L.d.cts} +32 -28
- package/dist/ws/index.cjs +6 -6
- package/dist/ws/index.js +2 -2
- package/package.json +22 -4
- package/dist/bootstrap-AKAUP6F6.cjs +0 -32
- package/dist/bootstrap-JCML6NFO.js +0 -7
- package/dist/chunk-2KVHZE6O.cjs.map +0 -1
- package/dist/chunk-2OL4O2TH.cjs.map +0 -1
- package/dist/chunk-35U3FROB.js.map +0 -1
- package/dist/chunk-3AJE4SEG.js.map +0 -1
- package/dist/chunk-3J4MFTI3.js +0 -3872
- package/dist/chunk-3J4MFTI3.js.map +0 -1
- package/dist/chunk-3TPQ2BU6.js.map +0 -1
- package/dist/chunk-3ZFYL34R.js.map +0 -1
- package/dist/chunk-4DA7QPLA.cjs.map +0 -1
- package/dist/chunk-57P6MJKC.js.map +0 -1
- package/dist/chunk-5KVM3WEY.cjs.map +0 -1
- package/dist/chunk-6IMPH6WV.cjs +0 -3897
- package/dist/chunk-6IMPH6WV.cjs.map +0 -1
- package/dist/chunk-ATBOUGQP.cjs +0 -513
- package/dist/chunk-ATBOUGQP.cjs.map +0 -1
- package/dist/chunk-DVD5P72E.cjs.map +0 -1
- package/dist/chunk-DXHRBMGB.js.map +0 -1
- package/dist/chunk-ES5HNFFT.js.map +0 -1
- package/dist/chunk-FXYP2HA6.js.map +0 -1
- package/dist/chunk-H727JIG7.js.map +0 -1
- package/dist/chunk-HXRD4B37.js.map +0 -1
- package/dist/chunk-I7HHI6QV.cjs.map +0 -1
- package/dist/chunk-IA6AU5PI.cjs.map +0 -1
- package/dist/chunk-IBG6V56E.cjs.map +0 -1
- package/dist/chunk-K7JPTH3G.cjs.map +0 -1
- package/dist/chunk-LINKCEG4.cjs.map +0 -1
- package/dist/chunk-OHVB4AJ7.js.map +0 -1
- package/dist/chunk-PDYFVNUX.cjs.map +0 -1
- package/dist/chunk-Q23JB3KL.js +0 -488
- package/dist/chunk-Q23JB3KL.js.map +0 -1
- package/dist/chunk-QPPDLRNR.js.map +0 -1
- package/dist/chunk-QUW2RZTM.cjs.map +0 -1
- package/dist/chunk-QXIQWPAP.js.map +0 -1
- package/dist/chunk-R3XIBBAW.cjs +0 -34
- package/dist/chunk-R3XIBBAW.cjs.map +0 -1
- package/dist/chunk-REK7AYOC.js.map +0 -1
- package/dist/chunk-SA7NSSIQ.cjs.map +0 -1
- package/dist/chunk-SDMNUYVU.js +0 -30
- package/dist/chunk-SDMNUYVU.js.map +0 -1
- package/dist/chunk-V3LKPM3O.cjs.map +0 -1
- package/dist/chunk-VJT6P4N6.cjs.map +0 -1
- package/dist/chunk-WOWUL7ZY.js.map +0 -1
- package/dist/chunk-WQBRWOQT.cjs.map +0 -1
- package/dist/chunk-Y3N7UUDO.js.map +0 -1
- package/dist/chunk-Y3QQN7PN.js.map +0 -1
- package/dist/chunk-YVUJBEXE.cjs.map +0 -1
- package/dist/index-CLp-DRKA.d.ts +0 -64
- package/dist/index-DfO7G4kN.d.cts +0 -64
- package/dist/media-GPPTZ43E.js +0 -4
- package/dist/mongo-auth-adapter-NHHUJHVH.cjs +0 -17
- package/dist/mongo-auth-adapter-NJQUUCTP.js +0 -4
- package/dist/postgres-auth-adapter-3T2NKTSE.js +0 -5
- package/dist/postgres-auth-adapter-7IEENCKQ.cjs +0 -14
- package/dist/redis-adapter-D2E2S3GB.cjs +0 -13
- package/dist/redis-adapter-VQXD7ESY.js +0 -4
- package/dist/sqlite-adapter-LVK5PS4T.cjs +0 -13
- package/dist/sqlite-adapter-TR3U3W6Q.js +0 -4
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
-
|
|
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.
|
|
430
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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: ${
|
|
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
|
|
1185
|
-
collection: "
|
|
1186
|
-
where: {
|
|
1578
|
+
const doc = await this.db.findOne({
|
|
1579
|
+
collection: "_globals_access-settings",
|
|
1580
|
+
where: {}
|
|
1187
1581
|
});
|
|
1188
|
-
if (
|
|
1189
|
-
this.settings =
|
|
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
|
-
|
|
1627
|
+
const defaultSchema = buildGraphQLSchema({
|
|
1214
1628
|
registry: this.registry,
|
|
1215
1629
|
db: this.db,
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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?.
|
|
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(`
|
|
1412
|
-
columns.push(`
|
|
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}
|
|
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 === "
|
|
2036
|
+
if (colName === "status") {
|
|
1440
2037
|
this.db.exec(`ALTER TABLE ${name} ADD COLUMN ${this.col(colName)} TEXT DEFAULT 'published'`);
|
|
1441
|
-
} else if (colName === "
|
|
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
|
|
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
|
|
1575
|
-
conditions.push(`
|
|
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
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
|
1662
|
-
sql += ` AND
|
|
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
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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("
|
|
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
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
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-
|
|
2236
|
-
//# sourceMappingURL=chunk-
|
|
2765
|
+
//# sourceMappingURL=chunk-5H3MWQJS.js.map
|
|
2766
|
+
//# sourceMappingURL=chunk-5H3MWQJS.js.map
|