@vexcms/core 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,3158 @@
1
+ // src/types/media.ts
2
+ var LOCKED_MEDIA_FIELDS = [
3
+ "storageId",
4
+ "filename",
5
+ "mimeType",
6
+ "size"
7
+ ];
8
+ var OVERRIDABLE_MEDIA_FIELDS = [
9
+ "url",
10
+ "alt",
11
+ "width",
12
+ "height"
13
+ ];
14
+ function getDefaultMediaFields() {
15
+ return {
16
+ storageId: {
17
+ type: "text",
18
+ required: true,
19
+ defaultValue: "",
20
+ label: "Storage ID",
21
+ admin: { hidden: true }
22
+ },
23
+ filename: {
24
+ type: "text",
25
+ required: true,
26
+ defaultValue: "",
27
+ label: "Filename",
28
+ admin: { readOnly: true }
29
+ },
30
+ mimeType: {
31
+ type: "text",
32
+ required: true,
33
+ defaultValue: "",
34
+ label: "MIME Type",
35
+ index: "by_mimeType",
36
+ admin: { readOnly: true }
37
+ },
38
+ size: {
39
+ type: "number",
40
+ required: true,
41
+ defaultValue: 0,
42
+ label: "File Size (bytes)",
43
+ admin: { readOnly: true }
44
+ },
45
+ url: {
46
+ type: "text",
47
+ required: true,
48
+ defaultValue: "",
49
+ label: "URL",
50
+ admin: { readOnly: true }
51
+ },
52
+ alt: { type: "text", label: "Alt Text" },
53
+ width: { type: "number", label: "Width (px)" },
54
+ height: { type: "number", label: "Height (px)" }
55
+ };
56
+ }
57
+
58
+ // src/errors/index.ts
59
+ var VexError = class extends Error {
60
+ constructor(message) {
61
+ super(`[vex] ${message}`);
62
+ this.name = "VexError";
63
+ }
64
+ };
65
+ var VexSlugConflictError = class extends VexError {
66
+ constructor(slug, existingSource, existingLocation, newSource, newLocation) {
67
+ super(
68
+ `Duplicate table slug "${slug}":
69
+ - ${existingSource}: ${existingLocation}
70
+ - ${newSource}: ${newLocation}
71
+ Rename one of these to resolve the conflict.`
72
+ );
73
+ this.slug = slug;
74
+ this.existingSource = existingSource;
75
+ this.existingLocation = existingLocation;
76
+ this.newSource = newSource;
77
+ this.newLocation = newLocation;
78
+ this.name = "VexSlugConflictError";
79
+ }
80
+ };
81
+ var VexFieldValidationError = class extends VexError {
82
+ constructor(collectionSlug, fieldName, detail) {
83
+ super(`Field "${fieldName}" in collection "${collectionSlug}": ${detail}`);
84
+ this.collectionSlug = collectionSlug;
85
+ this.fieldName = fieldName;
86
+ this.detail = detail;
87
+ this.name = "VexFieldValidationError";
88
+ }
89
+ };
90
+ var VexAuthConfigError = class extends VexError {
91
+ constructor(detail) {
92
+ super(`Auth configuration error: ${detail}`);
93
+ this.name = "VexAuthConfigError";
94
+ }
95
+ };
96
+ var VexMediaConfigError = class extends VexError {
97
+ constructor(detail) {
98
+ super(`Media configuration error: ${detail}`);
99
+ this.name = "VexMediaConfigError";
100
+ }
101
+ };
102
+ var VexAccessConfigError = class extends VexError {
103
+ constructor(detail) {
104
+ super(`Access configuration error: ${detail}`);
105
+ this.name = "VexAccessConfigError";
106
+ }
107
+ };
108
+ var VexAccessError = class extends VexError {
109
+ constructor(resource, action, field) {
110
+ const target = field ? `field "${field}" on resource "${resource}"` : `resource "${resource}"`;
111
+ super(`Access denied: ${action} on ${target}`);
112
+ this.resource = resource;
113
+ this.action = action;
114
+ this.field = field;
115
+ this.name = "VexAccessError";
116
+ }
117
+ };
118
+ var VexBlockValidationError = class extends VexError {
119
+ constructor(blockSlug, detail) {
120
+ super(`Block "${blockSlug}": ${detail}`);
121
+ this.blockSlug = blockSlug;
122
+ this.detail = detail;
123
+ this.name = "VexBlockValidationError";
124
+ }
125
+ };
126
+
127
+ // src/config/defineConfig.ts
128
+ var BASE_VEX_CONFIG = {
129
+ basePath: "/admin",
130
+ globals: [],
131
+ collections: [],
132
+ admin: {
133
+ meta: {
134
+ titleSuffix: "| Admin",
135
+ favicon: "/favicon.ico"
136
+ },
137
+ user: "users",
138
+ sidebar: {
139
+ hideGlobals: false
140
+ }
141
+ },
142
+ schema: {
143
+ outputPath: "/convex/vex.schema.ts",
144
+ typesOutputPath: "/convex/vex.types.ts",
145
+ autoMigrate: true,
146
+ autoRemove: false
147
+ }
148
+ };
149
+ function resolveMediaCollection(props) {
150
+ const defaults = getDefaultMediaFields();
151
+ if (props.mediaCollection.fields) {
152
+ for (const [fieldName, field] of Object.entries(props.mediaCollection.fields)) {
153
+ if (LOCKED_MEDIA_FIELDS.includes(fieldName)) {
154
+ if (process.env.NODE_ENV !== "production") {
155
+ console.warn(
156
+ `[vex] Media collection "${props.mediaCollection.slug}": field "${fieldName}" is a system field and cannot be overridden`
157
+ );
158
+ }
159
+ continue;
160
+ }
161
+ defaults[fieldName] = field;
162
+ }
163
+ }
164
+ const adminConfig = {
165
+ ...props.mediaCollection.admin,
166
+ useAsTitle: props.mediaCollection.admin?.useAsTitle ?? "filename"
167
+ };
168
+ return {
169
+ slug: props.mediaCollection.slug,
170
+ fields: defaults,
171
+ tableName: props.mediaCollection.tableName,
172
+ labels: props.mediaCollection.labels,
173
+ admin: adminConfig
174
+ };
175
+ }
176
+ function defineConfig(vexConfig) {
177
+ const { media: mediaInput, ...restInput } = vexConfig;
178
+ const config = {
179
+ ...BASE_VEX_CONFIG,
180
+ ...restInput,
181
+ admin: {
182
+ ...BASE_VEX_CONFIG.admin,
183
+ ...vexConfig.admin,
184
+ meta: {
185
+ ...BASE_VEX_CONFIG.admin.meta,
186
+ ...vexConfig.admin?.meta
187
+ },
188
+ sidebar: {
189
+ ...BASE_VEX_CONFIG.admin.sidebar,
190
+ ...vexConfig.admin?.sidebar
191
+ },
192
+ livePreview: vexConfig.admin?.livePreview
193
+ },
194
+ schema: {
195
+ ...BASE_VEX_CONFIG.schema,
196
+ ...vexConfig.schema
197
+ },
198
+ access: vexConfig.access
199
+ };
200
+ if (mediaInput) {
201
+ if (mediaInput.collections.length === 0) {
202
+ config.media = void 0;
203
+ } else if (!mediaInput.storageAdapter) {
204
+ throw new VexMediaConfigError(
205
+ "media.storageAdapter is required when media.collections is non-empty"
206
+ );
207
+ } else {
208
+ config.media = {
209
+ collections: mediaInput.collections.map(
210
+ (mc) => resolveMediaCollection({ mediaCollection: mc })
211
+ ),
212
+ storageAdapter: mediaInput.storageAdapter
213
+ };
214
+ }
215
+ } else {
216
+ config.media = void 0;
217
+ }
218
+ if (process.env.NODE_ENV !== "production") {
219
+ for (const collection of config.collections) {
220
+ if (!/^[a-z][a-z0-9_]*$/.test(collection.slug)) {
221
+ console.warn(
222
+ `[vex] Collection slug "${collection.slug}" should be lowercase alphanumeric with underscores, starting with a letter`
223
+ );
224
+ }
225
+ if (collection.slug.startsWith("vex_")) {
226
+ console.warn(
227
+ `[vex] Collection slug "${collection.slug}" uses reserved prefix "vex_"`
228
+ );
229
+ }
230
+ if (Object.keys(collection.fields).length === 0) {
231
+ console.warn(`[vex] Collection "${collection.slug}" has no fields defined`);
232
+ }
233
+ }
234
+ for (const global of config.globals) {
235
+ if (!/^[a-z][a-z0-9_]*$/.test(global.slug)) {
236
+ console.warn(
237
+ `[vex] Global slug "${global.slug}" should be lowercase alphanumeric with underscores, starting with a letter`
238
+ );
239
+ }
240
+ if (global.slug.startsWith("vex_")) {
241
+ console.warn(`[vex] Global slug "${global.slug}" uses reserved prefix "vex_"`);
242
+ }
243
+ if (Object.keys(global.fields).length === 0) {
244
+ console.warn(`[vex] Global "${global.slug}" has no fields defined`);
245
+ }
246
+ }
247
+ const slugs = config.collections.concat(config.globals).map((c) => c.slug);
248
+ const duplicates = slugs.filter((slug, i) => slugs.indexOf(slug) !== i);
249
+ if (duplicates.length > 0) {
250
+ console.warn(
251
+ `[vex] Duplicate collection slugs detected: ${duplicates.join(", ")}`
252
+ );
253
+ }
254
+ }
255
+ return config;
256
+ }
257
+
258
+ // src/config/defineCollection.ts
259
+ function defineCollection(props) {
260
+ const { auth: _auth, ...rest } = props;
261
+ return rest;
262
+ }
263
+ function defineMediaCollection(props) {
264
+ return props;
265
+ }
266
+
267
+ // src/access/defineAccess.ts
268
+ function defineAccess(props) {
269
+ if (props.orgCollection && !props.userOrgField) {
270
+ throw new VexAccessConfigError("orgCollection requires userOrgField");
271
+ }
272
+ if (props.userOrgField && !props.orgCollection) {
273
+ throw new VexAccessConfigError("userOrgField requires orgCollection");
274
+ }
275
+ const adminRoles = props.adminRoles ?? props.roles;
276
+ if (process.env.NODE_ENV !== "production") {
277
+ if (!props.userCollection?.slug) {
278
+ console.warn("[vex] defineAccess: userCollection must have a slug");
279
+ }
280
+ if (props.orgCollection && !props.orgCollection.slug) {
281
+ console.warn("[vex] defineAccess: orgCollection must have a slug");
282
+ }
283
+ if (props.resources) {
284
+ const resourceSlugs = new Set(
285
+ props.resources.map((r) => r.slug)
286
+ );
287
+ for (const role of Object.keys(props.permissions)) {
288
+ const rolePerms = props.permissions[role];
289
+ if (!rolePerms) continue;
290
+ for (const slug of Object.keys(rolePerms)) {
291
+ if (!resourceSlugs.has(slug)) {
292
+ console.warn(
293
+ `[vex] defineAccess: permission resource "${slug}" not found in resources`
294
+ );
295
+ }
296
+ }
297
+ }
298
+ }
299
+ const rolesSet = new Set(props.roles);
300
+ for (const role of Object.keys(props.permissions)) {
301
+ if (!rolesSet.has(role)) {
302
+ console.warn(
303
+ `[vex] defineAccess: permission role "${role}" not in roles array`
304
+ );
305
+ }
306
+ }
307
+ if (props.adminRoles) {
308
+ const rolesSetForAdmin = new Set(props.roles);
309
+ for (const adminRole of props.adminRoles) {
310
+ if (!rolesSetForAdmin.has(adminRole)) {
311
+ console.warn(
312
+ `[vex] defineAccess: adminRole "${adminRole}" not found in roles array`
313
+ );
314
+ }
315
+ }
316
+ }
317
+ if (props.userOrgField && props.userCollection?.fields) {
318
+ if (!(props.userOrgField in props.userCollection.fields)) {
319
+ console.warn(
320
+ `[vex] defineAccess: userOrgField "${props.userOrgField}" not found in user collection fields`
321
+ );
322
+ }
323
+ }
324
+ }
325
+ return {
326
+ roles: props.roles,
327
+ adminRoles,
328
+ userCollection: props.userCollection.slug,
329
+ orgCollection: props.orgCollection?.slug,
330
+ userOrgField: props.userOrgField,
331
+ permissions: props.permissions
332
+ };
333
+ }
334
+
335
+ // src/access/hasPermission.ts
336
+ function resolvePermissionCheck(props) {
337
+ if (props.check === void 0) {
338
+ if (props.fields === void 0) return true;
339
+ return Object.fromEntries(props.fields.map((k) => [k, true]));
340
+ }
341
+ let resolved;
342
+ if (typeof props.check === "function") {
343
+ const callbackProps = props.organization !== void 0 ? { data: props.data, user: props.user, organization: props.organization } : { data: props.data, user: props.user };
344
+ resolved = props.check(callbackProps);
345
+ } else {
346
+ resolved = props.check;
347
+ }
348
+ if (resolved === void 0) {
349
+ if (props.fields === void 0) return false;
350
+ return Object.fromEntries(props.fields.map((k) => [k, false]));
351
+ }
352
+ if (typeof resolved === "boolean") {
353
+ if (props.fields === void 0) return resolved;
354
+ return Object.fromEntries(props.fields.map((k) => [k, resolved]));
355
+ }
356
+ if (props.fields === void 0) {
357
+ if (resolved.mode === "allow") return resolved.fields.length > 0;
358
+ if (resolved.mode === "deny") return resolved.fields.length === 0;
359
+ return true;
360
+ }
361
+ if (resolved.mode === "allow") {
362
+ const allowSet = new Set(resolved.fields);
363
+ return Object.fromEntries(
364
+ props.fields.map((k) => [k, allowSet.has(k)])
365
+ );
366
+ }
367
+ const denySet = new Set(resolved.fields);
368
+ return Object.fromEntries(
369
+ props.fields.map((k) => [k, !denySet.has(k)])
370
+ );
371
+ }
372
+ function mergeRolePermissions(props) {
373
+ if (props.results.length === 0) {
374
+ if (props.fields === void 0) return true;
375
+ return Object.fromEntries(props.fields.map((k) => [k, true]));
376
+ }
377
+ if (props.fields === void 0) {
378
+ return props.results.some((r) => r === true);
379
+ }
380
+ return Object.fromEntries(
381
+ props.fields.map((k) => [
382
+ k,
383
+ props.results.some(
384
+ (r) => typeof r === "boolean" ? r : r[k] === true
385
+ )
386
+ ])
387
+ );
388
+ }
389
+ function hasPermission(props) {
390
+ if (props.access === void 0) {
391
+ if (props.fields === void 0) return true;
392
+ return Object.fromEntries(props.fields.map((k) => [k, true]));
393
+ }
394
+ if (props.userRoles.length === 0) {
395
+ if (props.throwOnDenied) {
396
+ throw new VexAccessError(props.resource, props.action);
397
+ }
398
+ if (props.fields === void 0) return false;
399
+ return Object.fromEntries(props.fields.map((k) => [k, false]));
400
+ }
401
+ const knownRolesSet = new Set(props.access.roles);
402
+ const knownRoles = props.userRoles.filter((r) => knownRolesSet.has(r));
403
+ if (knownRoles.length === 0) {
404
+ if (props.throwOnDenied) {
405
+ throw new VexAccessError(props.resource, props.action);
406
+ }
407
+ if (props.fields === void 0) return false;
408
+ return Object.fromEntries(props.fields.map((k) => [k, false]));
409
+ }
410
+ const results = [];
411
+ const data = props.data ?? {};
412
+ for (const role of knownRoles) {
413
+ const rolePerms = props.access.permissions[role];
414
+ if (rolePerms === void 0) {
415
+ continue;
416
+ }
417
+ const resourcePerms = rolePerms[props.resource];
418
+ if (resourcePerms === void 0) {
419
+ results.push(true);
420
+ continue;
421
+ }
422
+ if (typeof resourcePerms === "boolean") {
423
+ results.push(resourcePerms);
424
+ continue;
425
+ }
426
+ const actionCheck = resourcePerms[props.action];
427
+ results.push(
428
+ resolvePermissionCheck({
429
+ check: actionCheck,
430
+ fields: props.fields,
431
+ data,
432
+ user: props.user,
433
+ organization: props.organization
434
+ })
435
+ );
436
+ }
437
+ const merged = mergeRolePermissions({
438
+ results,
439
+ fields: props.fields
440
+ });
441
+ if (props.throwOnDenied) {
442
+ if (typeof merged === "boolean") {
443
+ if (!merged) {
444
+ throw new VexAccessError(props.resource, props.action);
445
+ }
446
+ } else {
447
+ const deniedField = Object.entries(merged).find(([, v2]) => v2 === false);
448
+ if (deniedField) {
449
+ throw new VexAccessError(props.resource, props.action, deniedField[0]);
450
+ }
451
+ }
452
+ }
453
+ return merged;
454
+ }
455
+
456
+ // src/config/sanitizeConfig.ts
457
+ function sanitizeConfigForClient(config) {
458
+ const { media, ...rest } = config;
459
+ return {
460
+ ...rest,
461
+ collections: rest.collections.map((collection) => {
462
+ if (!collection.admin?.livePreview) return collection;
463
+ if (typeof collection.admin.livePreview.url === "string") return collection;
464
+ return {
465
+ ...collection,
466
+ admin: {
467
+ ...collection.admin,
468
+ livePreview: {
469
+ ...collection.admin.livePreview,
470
+ url: null
471
+ }
472
+ }
473
+ };
474
+ }),
475
+ media: media ? { collections: media.collections } : void 0
476
+ };
477
+ }
478
+ function extractLivePreviewConfigs(config) {
479
+ const result = {};
480
+ for (const collection of config.collections) {
481
+ if (collection.admin?.livePreview && typeof collection.admin.livePreview.url === "function") {
482
+ result[collection.slug] = { url: collection.admin.livePreview.url };
483
+ }
484
+ }
485
+ return result;
486
+ }
487
+
488
+ // src/config/isMediaCollection.ts
489
+ function isMediaCollection(props) {
490
+ if (!props.config.media?.collections) return false;
491
+ return props.config.media.collections.some(
492
+ (mc) => mc.slug === props.collection.slug
493
+ );
494
+ }
495
+
496
+ // src/config/findCollectionBySlug.ts
497
+ function getAllCollections(props) {
498
+ const { config, excludeGlobals = false } = props;
499
+ const result = [];
500
+ for (const c of config.collections) {
501
+ result.push({ slug: c.slug, fields: c.fields, kind: "collection" });
502
+ }
503
+ if (config.media?.collections) {
504
+ for (const c of config.media.collections) {
505
+ result.push({ slug: c.slug, fields: c.fields, kind: "media" });
506
+ }
507
+ }
508
+ if (!excludeGlobals) {
509
+ for (const g of config.globals) {
510
+ result.push({ slug: g.slug, fields: g.fields, kind: "global" });
511
+ }
512
+ }
513
+ return result;
514
+ }
515
+ function findCollectionBySlug(props) {
516
+ return getAllCollections(props).find((c) => c.slug === props.slug) ?? null;
517
+ }
518
+
519
+ // src/fields/checkbox/config.ts
520
+ function checkbox(options) {
521
+ return {
522
+ type: "checkbox",
523
+ ...options?.required && options?.defaultValue === void 0 ? { defaultValue: false } : {},
524
+ ...options
525
+ };
526
+ }
527
+
528
+ // src/utils.ts
529
+ var MINOR_WORDS = /* @__PURE__ */ new Set([
530
+ "a",
531
+ "an",
532
+ "and",
533
+ "as",
534
+ // "at",
535
+ "but",
536
+ "by",
537
+ "for",
538
+ "if",
539
+ "in",
540
+ "nor",
541
+ "of",
542
+ "on",
543
+ "or",
544
+ "so",
545
+ "the",
546
+ "to",
547
+ "up",
548
+ "yet"
549
+ ]);
550
+ function toTitleCase(input) {
551
+ const words = input.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim().split(/\s+/);
552
+ return words.map((word, i) => {
553
+ const lower = word.toLowerCase();
554
+ if (i > 0 && MINOR_WORDS.has(lower)) return lower;
555
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
556
+ }).join(" ");
557
+ }
558
+
559
+ // src/fields/checkbox/columnDef.ts
560
+ function checkboxColumnDef(props) {
561
+ return {
562
+ accessorKey: props.fieldKey,
563
+ header: props.field.label ?? toTitleCase(props.fieldKey),
564
+ meta: { align: props.field.admin?.cellAlignment ?? "left" }
565
+ };
566
+ }
567
+
568
+ // src/valueTypes/processAdminOptions.ts
569
+ function processFieldValueTypeOptions(props) {
570
+ if (!props.field.required) {
571
+ return `v.optional(${props.valueType})`;
572
+ }
573
+ if (!props.skipDefaultValidation) {
574
+ if (props.field.defaultValue === void 0) {
575
+ throw new VexFieldValidationError(
576
+ props.collectionSlug,
577
+ props.fieldName,
578
+ "No defaultValue Provided"
579
+ );
580
+ }
581
+ if (!(typeof props.field.defaultValue === props.expectedType)) {
582
+ throw new VexFieldValidationError(
583
+ props.collectionSlug,
584
+ props.fieldName,
585
+ `Invalid defaultValue Provided. Expected: ${props.expectedType}, Received: ${typeof props.field.defaultValue}`
586
+ );
587
+ }
588
+ }
589
+ return props.valueType;
590
+ }
591
+
592
+ // src/fields/constants.ts
593
+ var TEXT_VALUETYPE = "v.string()";
594
+ var NUMBER_VALUETYPE = "v.number()";
595
+ var CHECKBOX_VALUETYPE = "v.boolean()";
596
+ var DATE_VALUETYPE = "v.number()";
597
+ var IMAGEURL_VALUETYPE = "v.string()";
598
+ var JSON_VALUETYPE = "v.any()";
599
+ var RICHTEXT_VALUETYPE = "v.any()";
600
+
601
+ // src/fields/checkbox/schemaValueType.ts
602
+ function checkboxToValueTypeString(props) {
603
+ return processFieldValueTypeOptions({
604
+ field: props.field,
605
+ collectionSlug: props.collectionSlug,
606
+ fieldName: props.fieldName,
607
+ expectedType: "boolean",
608
+ valueType: CHECKBOX_VALUETYPE
609
+ });
610
+ }
611
+
612
+ // src/fields/number/config.ts
613
+ function number(options) {
614
+ return {
615
+ type: "number",
616
+ ...options?.required && options?.defaultValue === void 0 ? { defaultValue: 0 } : {},
617
+ ...options
618
+ };
619
+ }
620
+
621
+ // src/fields/number/columnDef.ts
622
+ function numberColumnDef(props) {
623
+ return {
624
+ accessorKey: props.fieldKey,
625
+ header: props.field.label ?? toTitleCase(props.fieldKey),
626
+ meta: { align: props.field.admin?.cellAlignment ?? "right" }
627
+ };
628
+ }
629
+
630
+ // src/fields/number/schemaValueType.ts
631
+ function numberToValueTypeString(props) {
632
+ return processFieldValueTypeOptions({
633
+ field: props.field,
634
+ collectionSlug: props.collectionSlug,
635
+ fieldName: props.fieldName,
636
+ expectedType: "number",
637
+ valueType: NUMBER_VALUETYPE
638
+ });
639
+ }
640
+
641
+ // src/fields/select/config.ts
642
+ function select(options) {
643
+ return { type: "select", ...options };
644
+ }
645
+
646
+ // src/fields/select/columnDef.tsx
647
+ import { jsx } from "react/jsx-runtime";
648
+ var DEFAULT_BADGE_COLORS = [
649
+ "#3b82f6",
650
+ // blue
651
+ "#22c55e",
652
+ // green
653
+ "#a855f7",
654
+ // purple
655
+ "#f59e0b",
656
+ // amber
657
+ "#f43f5e",
658
+ // rose
659
+ "#06b6d4",
660
+ // cyan
661
+ "#6366f1",
662
+ // indigo
663
+ "#14b8a6",
664
+ // teal
665
+ "#f97316",
666
+ // orange
667
+ "#d946ef"
668
+ // fuchsia
669
+ ];
670
+ function selectColumnDef(props) {
671
+ const optionMap = new Map(
672
+ props.field.options.map((opt, i) => [
673
+ opt.value,
674
+ {
675
+ label: opt.label,
676
+ color: opt.badgeColor ?? DEFAULT_BADGE_COLORS[i % DEFAULT_BADGE_COLORS.length]
677
+ }
678
+ ])
679
+ );
680
+ return {
681
+ accessorKey: props.fieldKey,
682
+ header: (props.field.hasMany ? props.field.labels?.singular : props.field.label) ?? toTitleCase(props.fieldKey),
683
+ meta: {
684
+ align: props.field.admin?.cellAlignment ?? "left",
685
+ noTruncate: true
686
+ },
687
+ cell: (info) => {
688
+ const raw = info.getValue();
689
+ const values = Array.isArray(raw) ? raw : raw != null ? [String(raw)] : [];
690
+ if (values.length === 0) return null;
691
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 max-w-[240px] max-h-[60px] overflow-auto", children: values.map((v2) => {
692
+ const opt = optionMap.get(v2);
693
+ return /* @__PURE__ */ jsx(
694
+ "span",
695
+ {
696
+ className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium text-white shrink-0",
697
+ style: { backgroundColor: opt?.color },
698
+ children: opt?.label ?? v2
699
+ },
700
+ v2
701
+ );
702
+ }) });
703
+ }
704
+ };
705
+ }
706
+
707
+ // src/fields/select/schemaValueType.ts
708
+ function selectToValueTypeString(props) {
709
+ const literals = props.field.options.map((o) => `v.literal("${o.value}")`).join(",");
710
+ if (props.field.hasMany) {
711
+ return processFieldValueTypeOptions({
712
+ field: props.field,
713
+ collectionSlug: props.collectionSlug,
714
+ fieldName: props.fieldName,
715
+ expectedType: "object",
716
+ valueType: props.field.options.length === 1 ? `v.array(${literals})` : `v.array(v.union(${literals}))`,
717
+ skipDefaultValidation: true
718
+ });
719
+ }
720
+ return processFieldValueTypeOptions({
721
+ field: props.field,
722
+ collectionSlug: props.collectionSlug,
723
+ fieldName: props.fieldName,
724
+ expectedType: "string",
725
+ valueType: `v.union(${literals})`
726
+ });
727
+ }
728
+
729
+ // src/fields/text/config.ts
730
+ function text(options) {
731
+ return {
732
+ type: "text",
733
+ ...options?.required && options?.defaultValue === void 0 ? { defaultValue: "" } : {},
734
+ ...options
735
+ };
736
+ }
737
+
738
+ // src/fields/text/columnDef.tsx
739
+ function textColumnDef(props) {
740
+ return {
741
+ accessorKey: props.fieldKey,
742
+ header: props.field.label ?? toTitleCase(props.fieldKey),
743
+ meta: { align: props.field.admin?.cellAlignment ?? "left" }
744
+ };
745
+ }
746
+
747
+ // src/fields/text/schemaValueType.ts
748
+ function textToValueTypeString(props) {
749
+ return processFieldValueTypeOptions({
750
+ field: props.field,
751
+ collectionSlug: props.collectionSlug,
752
+ fieldName: props.fieldName,
753
+ expectedType: "string",
754
+ valueType: TEXT_VALUETYPE
755
+ });
756
+ }
757
+
758
+ // src/fields/date/config.ts
759
+ function date(options) {
760
+ return {
761
+ type: "date",
762
+ ...options?.required && options?.defaultValue === void 0 ? { defaultValue: 0 } : {},
763
+ ...options
764
+ };
765
+ }
766
+
767
+ // src/fields/date/columnDef.ts
768
+ function dateColumnDef(props) {
769
+ return {
770
+ accessorKey: props.fieldKey,
771
+ header: props.field.label ?? toTitleCase(props.fieldKey),
772
+ meta: {
773
+ align: props.field.admin?.cellAlignment ?? "left"
774
+ },
775
+ cell: (info) => {
776
+ const value = info.getValue();
777
+ if (value == null) return "";
778
+ return new Date(value).toLocaleDateString();
779
+ }
780
+ };
781
+ }
782
+
783
+ // src/fields/date/schemaValueType.ts
784
+ function dateToValueTypeString(props) {
785
+ return processFieldValueTypeOptions({
786
+ field: props.field,
787
+ collectionSlug: props.collectionSlug,
788
+ fieldName: props.fieldName,
789
+ expectedType: "number",
790
+ valueType: DATE_VALUETYPE
791
+ });
792
+ }
793
+
794
+ // src/fields/imageUrl/config.ts
795
+ function imageUrl(options) {
796
+ return {
797
+ type: "imageUrl",
798
+ ...options?.required && options?.defaultValue === void 0 ? { defaultValue: "" } : {},
799
+ ...options
800
+ };
801
+ }
802
+
803
+ // src/fields/imageUrl/columnDef.tsx
804
+ import { jsx as jsx2 } from "react/jsx-runtime";
805
+ function imageUrlColumnDef(props) {
806
+ return {
807
+ accessorKey: props.fieldKey,
808
+ header: props.field.label ?? toTitleCase(props.fieldKey),
809
+ meta: { align: props.field.admin?.cellAlignment ?? "center" },
810
+ cell: (info) => {
811
+ const value = info.getValue();
812
+ if (!value || typeof value !== "string") return "";
813
+ const size = props.field.width ?? 28;
814
+ const height = props.field.height ?? size;
815
+ return /* @__PURE__ */ jsx2(
816
+ "img",
817
+ {
818
+ src: value,
819
+ alt: "",
820
+ width: size,
821
+ height,
822
+ className: "rounded-full object-cover bg-muted",
823
+ style: { width: size, height },
824
+ loading: "lazy",
825
+ referrerPolicy: "no-referrer",
826
+ onError: (e) => {
827
+ e.currentTarget.style.display = "none";
828
+ }
829
+ }
830
+ );
831
+ }
832
+ };
833
+ }
834
+
835
+ // src/fields/imageUrl/schemaValueType.ts
836
+ function imageUrlToValueTypeString(props) {
837
+ return processFieldValueTypeOptions({
838
+ field: props.field,
839
+ collectionSlug: props.collectionSlug,
840
+ fieldName: props.fieldName,
841
+ expectedType: "string",
842
+ valueType: IMAGEURL_VALUETYPE
843
+ });
844
+ }
845
+
846
+ // src/fields/relationship/config.ts
847
+ function relationship(options) {
848
+ return { type: "relationship", ...options };
849
+ }
850
+
851
+ // src/fields/relationship/columnDef.ts
852
+ function relationshipColumnDef(props) {
853
+ return {
854
+ accessorKey: props.fieldKey,
855
+ header: (props.field.hasMany ? props.field.labels?.singular : props.field.label) ?? toTitleCase(props.fieldKey),
856
+ meta: { type: "relationship", to: props.field.to, align: props.field.admin?.cellAlignment ?? "left" },
857
+ cell: (info) => {
858
+ const value = info.getValue();
859
+ if (value == null) return "";
860
+ if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? "" : "s"}`;
861
+ return String(value);
862
+ }
863
+ };
864
+ }
865
+
866
+ // src/fields/relationship/schemaValueType.ts
867
+ function relationshipToValueTypeString(props) {
868
+ const idType = `v.id("${props.field.to}")`;
869
+ const baseValueType = props.field.hasMany ? `v.array(${idType})` : idType;
870
+ return processFieldValueTypeOptions({
871
+ field: props.field,
872
+ collectionSlug: props.collectionSlug,
873
+ fieldName: props.fieldName,
874
+ expectedType: "string",
875
+ valueType: baseValueType,
876
+ skipDefaultValidation: true
877
+ });
878
+ }
879
+
880
+ // src/fields/json/config.ts
881
+ function json(options) {
882
+ return {
883
+ type: "json",
884
+ ...options
885
+ };
886
+ }
887
+
888
+ // src/fields/json/columnDef.ts
889
+ function jsonColumnDef(props) {
890
+ return {
891
+ accessorKey: props.fieldKey,
892
+ header: props.field.label ?? toTitleCase(props.fieldKey),
893
+ meta: { align: props.field.admin?.cellAlignment ?? "left" },
894
+ cell: (info) => {
895
+ const value = info.getValue();
896
+ if (value == null) return "";
897
+ const str = JSON.stringify(value);
898
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
899
+ }
900
+ };
901
+ }
902
+
903
+ // src/fields/json/schemaValueType.ts
904
+ function jsonToValueTypeString(props) {
905
+ return processFieldValueTypeOptions({
906
+ field: props.field,
907
+ collectionSlug: props.collectionSlug,
908
+ fieldName: props.fieldName,
909
+ expectedType: "object",
910
+ valueType: JSON_VALUETYPE,
911
+ skipDefaultValidation: true
912
+ });
913
+ }
914
+
915
+ // src/fields/richtext/config.ts
916
+ function richtext(options) {
917
+ return {
918
+ type: "richtext",
919
+ ...options
920
+ };
921
+ }
922
+
923
+ // src/fields/richtext/schemaValueType.ts
924
+ function richtextToValueTypeString(props) {
925
+ return processFieldValueTypeOptions({
926
+ field: props.field,
927
+ collectionSlug: props.collectionSlug,
928
+ fieldName: props.fieldName,
929
+ expectedType: "object",
930
+ valueType: RICHTEXT_VALUETYPE,
931
+ skipDefaultValidation: true
932
+ });
933
+ }
934
+
935
+ // src/fields/richtext/columnDef.ts
936
+ function richtextColumnDef(props) {
937
+ return {
938
+ accessorKey: props.fieldKey,
939
+ header: props.field.label ?? toTitleCase(props.fieldKey),
940
+ meta: { align: props.field.admin?.cellAlignment ?? "left" },
941
+ cell: (info) => {
942
+ const value = info.getValue();
943
+ if (value == null) return "";
944
+ if (Array.isArray(value) && value.length === 0) return "";
945
+ return "Rich text";
946
+ }
947
+ };
948
+ }
949
+
950
+ // src/fields/media/config.ts
951
+ function upload(options) {
952
+ return { type: "upload", ...options };
953
+ }
954
+
955
+ // src/fields/media/schemaValueType.ts
956
+ function uploadToValueTypeString(props) {
957
+ const idType = `v.id("${props.field.to}")`;
958
+ const baseValueType = props.field.hasMany ? `v.array(${idType})` : idType;
959
+ return processFieldValueTypeOptions({
960
+ field: props.field,
961
+ collectionSlug: props.collectionSlug,
962
+ fieldName: props.fieldName,
963
+ expectedType: "string",
964
+ valueType: baseValueType,
965
+ skipDefaultValidation: true
966
+ });
967
+ }
968
+
969
+ // src/fields/array/config.ts
970
+ function array(options) {
971
+ return { type: "array", ...options };
972
+ }
973
+
974
+ // src/fields/array/columnDef.ts
975
+ function arrayColumnDef(props) {
976
+ return {
977
+ accessorKey: props.fieldKey,
978
+ header: props.field.label ?? toTitleCase(props.fieldKey),
979
+ meta: { align: props.field.admin?.cellAlignment ?? "left" },
980
+ cell: (info) => {
981
+ const value = info.getValue();
982
+ if (!Array.isArray(value) || value.length === 0) return "no items";
983
+ if (value.length === 1) return "1 item";
984
+ return `${value.length} items`;
985
+ }
986
+ };
987
+ }
988
+
989
+ // src/fields/array/schemaValueType.ts
990
+ function arrayToValueTypeString(props) {
991
+ const innerValueType = props.resolveInnerField({
992
+ field: props.field.field,
993
+ collectionSlug: props.collectionSlug,
994
+ fieldName: `${props.fieldName}[]`
995
+ });
996
+ const unwrapped = innerValueType.replace(/^v\.optional\((.+)\)$/, "$1");
997
+ const arrayType = `v.array(${unwrapped})`;
998
+ return processFieldValueTypeOptions({
999
+ field: props.field,
1000
+ collectionSlug: props.collectionSlug,
1001
+ fieldName: props.fieldName,
1002
+ expectedType: "object",
1003
+ valueType: arrayType,
1004
+ skipDefaultValidation: true
1005
+ });
1006
+ }
1007
+
1008
+ // src/fields/blocks/config.ts
1009
+ function blocks(props) {
1010
+ const seen = /* @__PURE__ */ new Set();
1011
+ for (const block of props.blocks) {
1012
+ if (seen.has(block.slug)) {
1013
+ throw new VexBlockValidationError(
1014
+ block.slug,
1015
+ `Duplicate block slug "${block.slug}" in blocks field. Each block in a blocks() field must have a unique slug.`
1016
+ );
1017
+ }
1018
+ seen.add(block.slug);
1019
+ }
1020
+ return {
1021
+ type: "blocks",
1022
+ blocks: props.blocks,
1023
+ labels: props.labels,
1024
+ min: props.min,
1025
+ max: props.max,
1026
+ label: props.label,
1027
+ description: props.description,
1028
+ required: props.required,
1029
+ admin: props.admin
1030
+ };
1031
+ }
1032
+
1033
+ // src/fields/blocks/schemaValueType.ts
1034
+ function blocksToValueTypeString(props) {
1035
+ const visited = props.visitedBlockSlugs ?? /* @__PURE__ */ new Set();
1036
+ if (props.field.blocks.length === 0) {
1037
+ return processFieldValueTypeOptions({
1038
+ field: props.field,
1039
+ collectionSlug: props.collectionSlug,
1040
+ fieldName: props.fieldName,
1041
+ expectedType: "object",
1042
+ valueType: "v.array(v.any())",
1043
+ skipDefaultValidation: true
1044
+ });
1045
+ }
1046
+ const objectTypes = [];
1047
+ for (const block of props.field.blocks) {
1048
+ if (visited.has(block.slug)) {
1049
+ throw new VexBlockValidationError(
1050
+ block.slug,
1051
+ `Circular block reference detected: block "${block.slug}" references itself (directly or through a cycle).`
1052
+ );
1053
+ }
1054
+ const blockVisited = new Set(visited);
1055
+ blockVisited.add(block.slug);
1056
+ const fieldEntries = [
1057
+ `blockType: v.literal("${block.slug}")`,
1058
+ `blockName: v.optional(v.string())`,
1059
+ `_key: v.string()`
1060
+ ];
1061
+ for (const [fieldName, field] of Object.entries(block.fields)) {
1062
+ const valueType = props.resolveInnerField({
1063
+ field,
1064
+ collectionSlug: props.collectionSlug,
1065
+ fieldName: `${props.fieldName}.${block.slug}.${fieldName}`,
1066
+ visitedBlockSlugs: blockVisited
1067
+ });
1068
+ fieldEntries.push(`${fieldName}: ${valueType}`);
1069
+ }
1070
+ objectTypes.push(`v.object({${fieldEntries.join(", ")}})`);
1071
+ }
1072
+ const innerType = objectTypes.length === 1 ? objectTypes[0] : `v.union(${objectTypes.join(", ")})`;
1073
+ const arrayType = `v.array(${innerType})`;
1074
+ return processFieldValueTypeOptions({
1075
+ field: props.field,
1076
+ collectionSlug: props.collectionSlug,
1077
+ fieldName: props.fieldName,
1078
+ expectedType: "object",
1079
+ valueType: arrayType,
1080
+ skipDefaultValidation: true
1081
+ });
1082
+ }
1083
+
1084
+ // src/fields/blocks/columnDef.ts
1085
+ function blocksColumnDef(props) {
1086
+ const singular = props.field.labels?.singular ?? "block";
1087
+ const plural = props.field.labels?.plural ?? "blocks";
1088
+ return {
1089
+ accessorKey: props.fieldKey,
1090
+ header: props.field.label ?? toTitleCase(props.fieldKey),
1091
+ meta: { align: props.field.admin?.cellAlignment ?? "left" },
1092
+ cell: (info) => {
1093
+ const value = info.getValue();
1094
+ if (!Array.isArray(value) || value.length === 0) return `no ${plural}`;
1095
+ if (value.length === 1) return `1 ${singular}`;
1096
+ return `${value.length} ${plural}`;
1097
+ }
1098
+ };
1099
+ }
1100
+
1101
+ // src/valueTypes/extract.ts
1102
+ function fieldToValueType(props) {
1103
+ const { field, collectionSlug, fieldName } = props;
1104
+ switch (field.type) {
1105
+ case "text":
1106
+ return textToValueTypeString({ field, collectionSlug, fieldName });
1107
+ case "number":
1108
+ return numberToValueTypeString({ field, collectionSlug, fieldName });
1109
+ case "checkbox":
1110
+ return checkboxToValueTypeString({ field, collectionSlug, fieldName });
1111
+ case "select":
1112
+ return selectToValueTypeString({ field, collectionSlug, fieldName });
1113
+ case "date":
1114
+ return dateToValueTypeString({ field, collectionSlug, fieldName });
1115
+ case "imageUrl":
1116
+ return imageUrlToValueTypeString({ field, collectionSlug, fieldName });
1117
+ case "relationship":
1118
+ return relationshipToValueTypeString({ field, collectionSlug, fieldName });
1119
+ case "upload":
1120
+ return uploadToValueTypeString({ field, collectionSlug, fieldName });
1121
+ case "json":
1122
+ return jsonToValueTypeString({ field, collectionSlug, fieldName });
1123
+ case "richtext":
1124
+ return richtextToValueTypeString({ field, collectionSlug, fieldName });
1125
+ case "array":
1126
+ return arrayToValueTypeString({
1127
+ field,
1128
+ collectionSlug,
1129
+ fieldName,
1130
+ resolveInnerField: fieldToValueType
1131
+ });
1132
+ case "blocks":
1133
+ return blocksToValueTypeString({
1134
+ field,
1135
+ collectionSlug,
1136
+ fieldName,
1137
+ resolveInnerField: (innerProps) => fieldToValueType({
1138
+ field: innerProps.field,
1139
+ collectionSlug: innerProps.collectionSlug,
1140
+ fieldName: innerProps.fieldName,
1141
+ visitedBlockSlugs: innerProps.visitedBlockSlugs
1142
+ }),
1143
+ visitedBlockSlugs: props.visitedBlockSlugs
1144
+ });
1145
+ case "ui":
1146
+ throw new VexFieldValidationError(
1147
+ collectionSlug,
1148
+ fieldName,
1149
+ `UI field "${fieldName}" on collection "${collectionSlug}" has no database representation and should not be included in schema generation.`
1150
+ );
1151
+ default:
1152
+ throw new VexFieldValidationError(
1153
+ collectionSlug,
1154
+ fieldName,
1155
+ `Unknown Field Type: ${field.type}`
1156
+ );
1157
+ }
1158
+ }
1159
+
1160
+ // src/valueTypes/indexes.ts
1161
+ function collectIndexes(props) {
1162
+ const { collection } = props;
1163
+ const fieldIndexes = /* @__PURE__ */ new Map();
1164
+ for (const [fieldKey, field] of Object.entries(collection.fields)) {
1165
+ const indexName = field.index;
1166
+ if (indexName) {
1167
+ if (fieldIndexes.has(indexName)) {
1168
+ throw new VexFieldValidationError(
1169
+ collection.slug,
1170
+ fieldKey,
1171
+ `Duplicate Indexes detected: ${indexName}`
1172
+ );
1173
+ }
1174
+ fieldIndexes.set(indexName, { name: indexName, fields: [fieldKey] });
1175
+ }
1176
+ }
1177
+ collection.indexes?.forEach((index) => {
1178
+ fieldIndexes.set(index.name, { name: index.name, fields: index.fields });
1179
+ });
1180
+ const useAsTitle = collection.admin?.useAsTitle;
1181
+ if (useAsTitle && useAsTitle !== "_id") {
1182
+ const autoName = `by_${useAsTitle}`;
1183
+ if (!fieldIndexes.has(autoName)) {
1184
+ fieldIndexes.set(autoName, { name: autoName, fields: [useAsTitle] });
1185
+ }
1186
+ }
1187
+ return Array.from(fieldIndexes.values());
1188
+ }
1189
+
1190
+ // src/valueTypes/searchIndexes.ts
1191
+ function collectSearchIndexes(props) {
1192
+ const { collection } = props;
1193
+ const searchIndexes = /* @__PURE__ */ new Map();
1194
+ for (const [fieldKey, field] of Object.entries(collection.fields)) {
1195
+ const searchIndex = field.searchIndex;
1196
+ if (searchIndex && searchIndex.name) {
1197
+ if (searchIndexes.has(searchIndex.name)) {
1198
+ throw new VexFieldValidationError(
1199
+ collection.slug,
1200
+ fieldKey,
1201
+ `Duplicate search index name: ${searchIndex.name}`
1202
+ );
1203
+ }
1204
+ searchIndexes.set(searchIndex.name, {
1205
+ name: searchIndex.name,
1206
+ searchField: fieldKey,
1207
+ filterFields: searchIndex.filterFields
1208
+ });
1209
+ }
1210
+ }
1211
+ collection.searchIndexes?.forEach((entry) => {
1212
+ searchIndexes.set(entry.name, {
1213
+ name: entry.name,
1214
+ searchField: entry.searchField,
1215
+ filterFields: entry.filterFields ?? []
1216
+ });
1217
+ });
1218
+ const useAsTitle = collection.admin?.useAsTitle;
1219
+ if (useAsTitle && useAsTitle !== "_id") {
1220
+ const autoName = `search_${useAsTitle}`;
1221
+ const alreadyCovered = Array.from(searchIndexes.values()).some(
1222
+ (si) => si.searchField === useAsTitle
1223
+ );
1224
+ if (!alreadyCovered && !searchIndexes.has(autoName)) {
1225
+ searchIndexes.set(autoName, {
1226
+ name: autoName,
1227
+ searchField: useAsTitle,
1228
+ filterFields: []
1229
+ });
1230
+ }
1231
+ }
1232
+ return Array.from(searchIndexes.values());
1233
+ }
1234
+
1235
+ // src/valueTypes/merge.ts
1236
+ function mergeAuthCollectionWithUserCollection(props) {
1237
+ const { authCollection, userCollection } = props;
1238
+ const fields = {};
1239
+ const overlapping = [];
1240
+ const authOnly = [];
1241
+ const userOnly = [];
1242
+ const authFields = authCollection.fields;
1243
+ const userFields = userCollection.fields;
1244
+ const authFieldKeys = Object.keys(authFields);
1245
+ const userFieldKeys = Object.keys(userFields);
1246
+ for (const fieldKey of authFieldKeys) {
1247
+ if (userFieldKeys.includes(fieldKey)) {
1248
+ overlapping.push(fieldKey);
1249
+ const authField = authFields[fieldKey];
1250
+ const userField = userFields[fieldKey];
1251
+ fields[fieldKey] = {
1252
+ ...userField,
1253
+ required: authField.required,
1254
+ ...authField.defaultValue !== void 0 && { defaultValue: authField.defaultValue }
1255
+ };
1256
+ } else {
1257
+ authOnly.push(fieldKey);
1258
+ fields[fieldKey] = authFields[fieldKey];
1259
+ }
1260
+ }
1261
+ for (const fieldKey of userFieldKeys) {
1262
+ if (authFieldKeys.includes(fieldKey)) continue;
1263
+ userOnly.push(fieldKey);
1264
+ fields[fieldKey] = userFields[fieldKey];
1265
+ }
1266
+ const indexes = [];
1267
+ const indexNames = /* @__PURE__ */ new Set();
1268
+ for (const idx of authCollection.indexes ?? []) {
1269
+ indexes.push({ name: idx.name, fields: idx.fields });
1270
+ indexNames.add(idx.name);
1271
+ }
1272
+ for (const idx of userCollection.indexes ?? []) {
1273
+ if (!indexNames.has(idx.name)) {
1274
+ indexes.push({ name: idx.name, fields: idx.fields });
1275
+ indexNames.add(idx.name);
1276
+ }
1277
+ }
1278
+ const searchIndexes = (userCollection.searchIndexes ?? []).map((si) => ({
1279
+ name: si.name,
1280
+ searchField: si.searchField,
1281
+ filterFields: si.filterFields ?? []
1282
+ }));
1283
+ return { fields, indexes, searchIndexes, overlapping, authOnly, userOnly };
1284
+ }
1285
+
1286
+ // src/valueTypes/slugs.ts
1287
+ var SLUG_SOURCES = {
1288
+ userCollection: "user-collection",
1289
+ userGlobal: "user-global",
1290
+ authTable: "auth-table",
1291
+ mediaCollection: "media-collection",
1292
+ system: "system"
1293
+ };
1294
+ var SlugRegistry = class {
1295
+ registrations = /* @__PURE__ */ new Map();
1296
+ /**
1297
+ * Register a slug with its source.
1298
+ * Throws VexSlugConflictError immediately if the slug is already registered,
1299
+ * UNLESS an auth table slug overlaps with a user collection slug — this is
1300
+ * expected behavior indicating the user wants to customize that auth table's
1301
+ * admin UI. In that case, the user collection's registration takes precedence
1302
+ * (it was registered first as "user-collection") and the auth table is
1303
+ * silently skipped in the registry. The merge happens during schema generation.
1304
+ *
1305
+ * @param props.slug - The table slug to register
1306
+ * @param props.source - Where this slug comes from (e.g., "user-collection", "auth-table")
1307
+ * @param props.location - Human-readable location for error messages (e.g., `collection "posts"`)
1308
+ *
1309
+ * Edge cases:
1310
+ * - Auth table slug matches user collection slug: NOT a conflict — skip
1311
+ * registration (user collection already registered, merge happens later)
1312
+ * - System table prefixed with "vex_" should not conflict with user tables
1313
+ * because defineCollection already warns about "vex_" prefix
1314
+ */
1315
+ register(props) {
1316
+ const existing = this.registrations.get(props.slug);
1317
+ if (existing) {
1318
+ if (existing.source === "user-collection" && props.source === "auth-table" || existing.source === "auth-table" && props.source === "user-collection") {
1319
+ if (props.source === "user-collection") {
1320
+ this.registrations.set(props.slug, {
1321
+ slug: props.slug,
1322
+ source: props.source,
1323
+ location: props.location
1324
+ });
1325
+ }
1326
+ return;
1327
+ }
1328
+ throw new VexSlugConflictError(
1329
+ props.slug,
1330
+ existing.source,
1331
+ existing.location,
1332
+ props.source,
1333
+ props.location
1334
+ );
1335
+ }
1336
+ this.registrations.set(props.slug, {
1337
+ slug: props.slug,
1338
+ source: props.source,
1339
+ location: props.location
1340
+ });
1341
+ }
1342
+ /**
1343
+ * Get all registered slugs.
1344
+ */
1345
+ getAll() {
1346
+ return [...this.registrations.values()];
1347
+ }
1348
+ };
1349
+ function buildSlugRegistry(props) {
1350
+ const registry = new SlugRegistry();
1351
+ for (const collection of props.config.collections) {
1352
+ registry.register({
1353
+ slug: collection.slug,
1354
+ source: SLUG_SOURCES.userCollection,
1355
+ location: `Collection ${collection.slug}`
1356
+ });
1357
+ }
1358
+ if (props.config.media) {
1359
+ for (const collection of props.config.media.collections) {
1360
+ registry.register({
1361
+ slug: collection.slug,
1362
+ source: SLUG_SOURCES.mediaCollection,
1363
+ location: `Media Collection ${collection.slug}`
1364
+ });
1365
+ }
1366
+ }
1367
+ for (const global of props.config.globals) {
1368
+ registry.register({
1369
+ slug: global.slug,
1370
+ source: SLUG_SOURCES.userGlobal,
1371
+ location: `Global ${global.slug}`
1372
+ });
1373
+ }
1374
+ for (const collection of props.config.auth.collections) {
1375
+ registry.register({
1376
+ slug: collection.slug,
1377
+ source: SLUG_SOURCES.authTable,
1378
+ location: `Auth Table ${collection.slug}`
1379
+ });
1380
+ }
1381
+ return registry;
1382
+ }
1383
+
1384
+ // src/valueTypes/generate.ts
1385
+ function generateVexSchema(props) {
1386
+ const config = props.config;
1387
+ buildSlugRegistry({ config });
1388
+ const authCollectionMap = new Map(
1389
+ config.auth.collections.map((c) => [c.slug, c])
1390
+ );
1391
+ const mergedAuthSlugs = /* @__PURE__ */ new Set();
1392
+ const lines = [
1393
+ "// \u26A0\uFE0F AUTO-GENERATED BY VEX CMS \u2014 DO NOT EDIT \u26A0\uFE0F",
1394
+ "",
1395
+ 'import { defineTable } from "convex/server";',
1396
+ 'import { v } from "convex/values";'
1397
+ ];
1398
+ if (config.collections.length > 0) {
1399
+ lines.push("", "/**", " * USER COLLECTIONS", " **/");
1400
+ }
1401
+ for (const collection of config.collections) {
1402
+ const authCollection = authCollectionMap.get(collection.slug);
1403
+ const fields = [];
1404
+ const indexes = collectIndexes({ collection });
1405
+ const searchIndexes = collectSearchIndexes({
1406
+ collection
1407
+ });
1408
+ if (authCollection) {
1409
+ mergedAuthSlugs.add(authCollection.slug);
1410
+ const merged = mergeAuthCollectionWithUserCollection({
1411
+ authCollection,
1412
+ userCollection: collection
1413
+ });
1414
+ for (const [name, field] of Object.entries(merged.fields)) {
1415
+ if (field.type === "ui") continue;
1416
+ fields.push({
1417
+ name,
1418
+ valueType: fieldToValueType({
1419
+ field,
1420
+ collectionSlug: collection.slug,
1421
+ fieldName: name
1422
+ })
1423
+ });
1424
+ }
1425
+ for (const index of merged.indexes) {
1426
+ if (indexes.find((ui2) => ui2.name === index.name)) continue;
1427
+ indexes.push(index);
1428
+ }
1429
+ for (const si of merged.searchIndexes) {
1430
+ if (searchIndexes.find((existing) => existing.name === si.name))
1431
+ continue;
1432
+ searchIndexes.push(si);
1433
+ }
1434
+ } else {
1435
+ for (const [fieldName, field] of Object.entries(
1436
+ collection.fields
1437
+ )) {
1438
+ if (field.type === "ui") continue;
1439
+ fields.push({
1440
+ name: fieldName,
1441
+ valueType: fieldToValueType({
1442
+ fieldName,
1443
+ field,
1444
+ collectionSlug: collection.slug
1445
+ })
1446
+ });
1447
+ }
1448
+ }
1449
+ lines.push(
1450
+ "",
1451
+ `export const ${collection.tableName ?? collection.slug} = defineTable({`
1452
+ );
1453
+ for (const f of fields) {
1454
+ lines.push(` ${f.name}: ${f.valueType},`);
1455
+ }
1456
+ lines.push(` vex_status: v.optional(v.union(v.literal("draft"), v.literal("published"))),`);
1457
+ if (collection.versions?.drafts) {
1458
+ lines.push(` vex_version: v.optional(v.number()),`);
1459
+ lines.push(` vex_publishedAt: v.optional(v.number()),`);
1460
+ }
1461
+ lines.push("})");
1462
+ for (const i of indexes) {
1463
+ const fieldList = i.fields.map((f) => `"${f}"`).join(", ");
1464
+ lines.push(` .index("${i.name}", [${fieldList}])`);
1465
+ }
1466
+ for (const si of searchIndexes) {
1467
+ const filterList = si.filterFields.length > 0 ? `, filterFields: [${si.filterFields.map((f) => `"${f}"`).join(", ")}]` : "";
1468
+ lines.push(
1469
+ ` .searchIndex("${si.name}", { searchField: "${si.searchField}"${filterList} })`
1470
+ );
1471
+ }
1472
+ }
1473
+ if (config.media && config.media.collections.length > 0) {
1474
+ lines.push("", "/**", " * MEDIA COLLECTIONS", " **/");
1475
+ for (const mediaCollection of config.media.collections) {
1476
+ const fields = [];
1477
+ const indexes = collectIndexes({ collection: mediaCollection });
1478
+ const searchIndexes = collectSearchIndexes({
1479
+ collection: mediaCollection
1480
+ });
1481
+ for (const [fieldName, field] of Object.entries(
1482
+ mediaCollection.fields
1483
+ )) {
1484
+ if (field.type === "ui") continue;
1485
+ if (fieldName === "storageId") {
1486
+ fields.push({
1487
+ name: fieldName,
1488
+ valueType: config.media.storageAdapter.storageIdValueType
1489
+ });
1490
+ } else {
1491
+ fields.push({
1492
+ name: fieldName,
1493
+ valueType: fieldToValueType({
1494
+ fieldName,
1495
+ field,
1496
+ collectionSlug: mediaCollection.slug
1497
+ })
1498
+ });
1499
+ }
1500
+ }
1501
+ lines.push(
1502
+ "",
1503
+ `export const ${mediaCollection.tableName ?? mediaCollection.slug} = defineTable({`
1504
+ );
1505
+ for (const f of fields) {
1506
+ lines.push(` ${f.name}: ${f.valueType},`);
1507
+ }
1508
+ lines.push("})");
1509
+ for (const i of indexes) {
1510
+ const fieldList = i.fields.map((f) => `"${f}"`).join(", ");
1511
+ lines.push(` .index("${i.name}", [${fieldList}])`);
1512
+ }
1513
+ for (const si of searchIndexes) {
1514
+ const filterList = si.filterFields.length > 0 ? `, filterFields: [${si.filterFields.map((f) => `"${f}"`).join(", ")}]` : "";
1515
+ lines.push(
1516
+ ` .searchIndex("${si.name}", { searchField: "${si.searchField}"${filterList} })`
1517
+ );
1518
+ }
1519
+ }
1520
+ }
1521
+ const unmergedAuthCollections = config.auth.collections.filter(
1522
+ (c) => !mergedAuthSlugs.has(c.slug)
1523
+ );
1524
+ if (unmergedAuthCollections.length > 0) {
1525
+ lines.push("", "/**", " * AUTH TABLES", " **/");
1526
+ }
1527
+ for (const authCollection of unmergedAuthCollections) {
1528
+ const indexes = collectIndexes({
1529
+ collection: authCollection
1530
+ });
1531
+ lines.push(
1532
+ "",
1533
+ `export const ${authCollection.tableName ?? authCollection.slug} = defineTable({`
1534
+ );
1535
+ for (const [name, field] of Object.entries(authCollection.fields)) {
1536
+ lines.push(
1537
+ ` ${name}: ${fieldToValueType({
1538
+ field,
1539
+ collectionSlug: authCollection.slug,
1540
+ fieldName: name
1541
+ })},`
1542
+ );
1543
+ }
1544
+ lines.push("})");
1545
+ for (const i of indexes) {
1546
+ const fieldList = i.fields.map((f) => `"${f}"`).join(", ");
1547
+ lines.push(` .index("${i.name}", [${fieldList}])`);
1548
+ }
1549
+ }
1550
+ for (const global of config.globals) {
1551
+ const fields = [];
1552
+ const indexes = collectIndexes({ collection: global });
1553
+ for (const [fieldName, field] of Object.entries(global.fields)) {
1554
+ fields.push({
1555
+ name: fieldName,
1556
+ valueType: fieldToValueType({
1557
+ fieldName,
1558
+ field,
1559
+ collectionSlug: global.slug
1560
+ })
1561
+ });
1562
+ }
1563
+ lines.push(
1564
+ "",
1565
+ `export const ${global.tableName ?? global.slug} = defineTable({`
1566
+ );
1567
+ for (const f of fields) {
1568
+ lines.push(` ${f.name}: ${f.valueType},`);
1569
+ }
1570
+ lines.push("})");
1571
+ for (const i of indexes) {
1572
+ const fieldList = i.fields.map((f) => `"${f}"`).join(", ");
1573
+ lines.push(` .index("${i.name}", [${fieldList}])`);
1574
+ }
1575
+ }
1576
+ {
1577
+ lines.push("", "/**", " * VEX SYSTEM TABLES", " **/");
1578
+ lines.push("");
1579
+ lines.push("export const vex_versions = defineTable({");
1580
+ lines.push(" collection: v.string(),");
1581
+ lines.push(" documentId: v.string(),");
1582
+ lines.push(" version: v.number(),");
1583
+ lines.push(` status: v.union(v.literal("draft"), v.literal("published"), v.literal("autosave"), v.literal("previewSnapshot")),`);
1584
+ lines.push(" snapshot: v.any(),");
1585
+ lines.push(" createdAt: v.number(),");
1586
+ lines.push(" createdBy: v.optional(v.string()),");
1587
+ lines.push(" isAutosave: v.boolean(),");
1588
+ lines.push(" restoredFrom: v.optional(v.number()),");
1589
+ lines.push("})");
1590
+ lines.push(` .index("by_document", ["collection", "documentId"])`);
1591
+ lines.push(` .index("by_document_version", ["collection", "documentId", "version"])`);
1592
+ lines.push(` .index("by_document_latest", ["collection", "documentId", "createdAt"])`);
1593
+ lines.push(` .index("by_document_status", ["collection", "documentId", "status"])`);
1594
+ lines.push(` .index("by_autosave", ["collection", "documentId", "isAutosave"])`);
1595
+ }
1596
+ return lines.join("\n") + "\n";
1597
+ }
1598
+
1599
+ // src/schema/extendTable.ts
1600
+ import { defineTable } from "convex/server";
1601
+ function extendTable(props) {
1602
+ const { validator } = props.table;
1603
+ let extended = defineTable({
1604
+ ...validator.fields,
1605
+ ...props.additionalFields
1606
+ });
1607
+ for (const idx of props.table[" indexes"]()) {
1608
+ extended = extended.index(
1609
+ idx.indexDescriptor,
1610
+ idx.fields
1611
+ );
1612
+ }
1613
+ const source = props.table;
1614
+ for (const idx of source.searchIndexes ?? []) {
1615
+ extended = extended.searchIndex(idx.indexDescriptor, {
1616
+ searchField: idx.searchField,
1617
+ filterFields: idx.filterFields
1618
+ });
1619
+ }
1620
+ for (const idx of source.vectorIndexes ?? []) {
1621
+ extended = extended.vectorIndex(idx.indexDescriptor, {
1622
+ vectorField: idx.vectorField,
1623
+ dimensions: idx.dimensions,
1624
+ filterFields: idx.filterFields
1625
+ });
1626
+ }
1627
+ return extended;
1628
+ }
1629
+
1630
+ // src/fields/media/columnDef.ts
1631
+ function uploadColumnDef(props) {
1632
+ return {
1633
+ accessorKey: props.fieldKey,
1634
+ header: (props.field.hasMany ? props.field.labels?.singular : props.field.label) ?? toTitleCase(props.fieldKey),
1635
+ meta: {
1636
+ type: "upload",
1637
+ to: props.field.to,
1638
+ noTruncate: true
1639
+ },
1640
+ cell: (info) => {
1641
+ const value = info.getValue();
1642
+ if (!value || typeof value !== "string") return "";
1643
+ return value;
1644
+ }
1645
+ };
1646
+ }
1647
+
1648
+ // src/columns/generateColumns.ts
1649
+ function generateColumns(props) {
1650
+ const { collection, auth } = props;
1651
+ const columns = [];
1652
+ const useAsTitle = collection.admin?.useAsTitle;
1653
+ const defaultColumns = collection.admin?.defaultColumns;
1654
+ const fields = collection.fields;
1655
+ const authFields = {};
1656
+ if (auth) {
1657
+ const authCollection = auth.collections.find(
1658
+ (c) => c.slug === collection.slug
1659
+ );
1660
+ if (authCollection) {
1661
+ for (const [k, v2] of Object.entries(authCollection.fields)) {
1662
+ authFields[k] = v2;
1663
+ }
1664
+ }
1665
+ }
1666
+ if (defaultColumns) {
1667
+ for (const fieldKey of defaultColumns) {
1668
+ if (fieldKey === "_id") {
1669
+ columns.push({ accessorKey: "_id", header: "ID" });
1670
+ continue;
1671
+ }
1672
+ const field = fields[fieldKey] ?? authFields[fieldKey];
1673
+ if (!field) {
1674
+ columns.push({ accessorKey: fieldKey, header: toTitleCase(fieldKey) });
1675
+ continue;
1676
+ }
1677
+ if (field.admin?.hidden) continue;
1678
+ if (field.type === "ui") continue;
1679
+ const col = buildColumnDef(fieldKey, field);
1680
+ if (useAsTitle && fieldKey === useAsTitle) {
1681
+ col.meta = { ...col.meta, isTitle: true };
1682
+ }
1683
+ if (field.admin?.components?.Cell) {
1684
+ col.meta = {
1685
+ ...col.meta,
1686
+ customCell: field.admin.components.Cell,
1687
+ fieldDef: field
1688
+ };
1689
+ }
1690
+ columns.push(col);
1691
+ }
1692
+ } else {
1693
+ columns.push({ accessorKey: "_id", header: "ID" });
1694
+ const allFieldKeys = new Set(Object.keys(fields));
1695
+ for (const k of Object.keys(authFields)) {
1696
+ allFieldKeys.add(k);
1697
+ }
1698
+ for (const fieldKey of allFieldKeys) {
1699
+ const field = fields[fieldKey] ?? authFields[fieldKey];
1700
+ if (field.admin?.hidden) continue;
1701
+ if (field.type === "ui") continue;
1702
+ const col = buildColumnDef(fieldKey, field);
1703
+ if (useAsTitle && fieldKey === useAsTitle) {
1704
+ col.meta = { ...col.meta, isTitle: true };
1705
+ }
1706
+ if (field.admin?.components?.Cell) {
1707
+ col.meta = {
1708
+ ...col.meta,
1709
+ customCell: field.admin.components.Cell,
1710
+ fieldDef: field
1711
+ };
1712
+ }
1713
+ columns.push(col);
1714
+ }
1715
+ }
1716
+ return columns;
1717
+ }
1718
+ function buildColumnDef(fieldKey, field) {
1719
+ switch (field.type) {
1720
+ case "text":
1721
+ return textColumnDef({ fieldKey, field });
1722
+ case "number":
1723
+ return numberColumnDef({ fieldKey, field });
1724
+ case "checkbox":
1725
+ return checkboxColumnDef({ fieldKey, field });
1726
+ case "select":
1727
+ return selectColumnDef({ fieldKey, field });
1728
+ case "date":
1729
+ return dateColumnDef({ fieldKey, field });
1730
+ case "imageUrl":
1731
+ return imageUrlColumnDef({ fieldKey, field });
1732
+ case "relationship":
1733
+ return relationshipColumnDef({ fieldKey, field });
1734
+ case "json":
1735
+ return jsonColumnDef({ fieldKey, field });
1736
+ case "richtext":
1737
+ return richtextColumnDef({ fieldKey, field });
1738
+ case "array":
1739
+ return arrayColumnDef({ fieldKey, field });
1740
+ case "upload":
1741
+ return uploadColumnDef({ fieldKey, field });
1742
+ case "blocks":
1743
+ return blocksColumnDef({ fieldKey, field });
1744
+ default:
1745
+ return {
1746
+ accessorKey: fieldKey,
1747
+ header: toTitleCase(fieldKey)
1748
+ };
1749
+ }
1750
+ }
1751
+
1752
+ // src/formSchema/generateFormSchema.ts
1753
+ import { z } from "zod";
1754
+ function generateFormSchema(props) {
1755
+ const shape = {};
1756
+ for (const [fieldName, field] of Object.entries(props.fields)) {
1757
+ if (field.admin?.hidden) continue;
1758
+ if (field.type === "ui") continue;
1759
+ let validator = fieldMetaToZod({ field });
1760
+ if (!field.required) {
1761
+ validator = validator.optional();
1762
+ }
1763
+ shape[fieldName] = validator;
1764
+ }
1765
+ return z.object(shape);
1766
+ }
1767
+ function fieldMetaToZod(props) {
1768
+ switch (props.field.type) {
1769
+ case "text": {
1770
+ let schema = z.string();
1771
+ if (props.field.minLength != null) schema = schema.min(props.field.minLength);
1772
+ if (props.field.maxLength != null) schema = schema.max(props.field.maxLength);
1773
+ return schema;
1774
+ }
1775
+ case "number": {
1776
+ let schema = z.number();
1777
+ if (props.field.min != null) schema = schema.min(props.field.min);
1778
+ if (props.field.max != null) schema = schema.max(props.field.max);
1779
+ return schema;
1780
+ }
1781
+ case "checkbox":
1782
+ return z.boolean();
1783
+ case "select": {
1784
+ const values = props.field.options.map((o) => o.value);
1785
+ if (values.length === 0) return z.string();
1786
+ const enumSchema = z.enum(values);
1787
+ if (props.field.hasMany) {
1788
+ return z.array(enumSchema);
1789
+ }
1790
+ return enumSchema;
1791
+ }
1792
+ case "date":
1793
+ return z.number();
1794
+ case "imageUrl":
1795
+ return z.string().url().or(z.literal(""));
1796
+ case "relationship": {
1797
+ if (props.field.hasMany) {
1798
+ return z.array(z.string());
1799
+ }
1800
+ return z.string();
1801
+ }
1802
+ case "upload": {
1803
+ if (props.field.hasMany) {
1804
+ return z.array(z.string());
1805
+ }
1806
+ return z.string();
1807
+ }
1808
+ case "json":
1809
+ return z.any();
1810
+ case "richtext":
1811
+ return z.any();
1812
+ case "array": {
1813
+ let schema = z.array(fieldMetaToZod({ field: props.field.field }));
1814
+ if (props.field.min != null) schema = schema.min(props.field.min);
1815
+ if (props.field.max != null) schema = schema.max(props.field.max);
1816
+ return schema;
1817
+ }
1818
+ case "blocks": {
1819
+ const blockSchemas = props.field.blocks.map((blockDef) => {
1820
+ const shape = {
1821
+ blockType: z.literal(blockDef.slug),
1822
+ blockName: z.string().optional(),
1823
+ _key: z.string()
1824
+ };
1825
+ for (const [fieldName, field] of Object.entries(blockDef.fields)) {
1826
+ let validator = fieldMetaToZod({ field });
1827
+ if (!field.required) {
1828
+ validator = validator.optional();
1829
+ }
1830
+ shape[fieldName] = validator;
1831
+ }
1832
+ return z.object(shape);
1833
+ });
1834
+ if (blockSchemas.length === 0) {
1835
+ let schema2 = z.array(z.any());
1836
+ if (props.field.min != null) schema2 = schema2.min(props.field.min);
1837
+ if (props.field.max != null) schema2 = schema2.max(props.field.max);
1838
+ return schema2;
1839
+ }
1840
+ const union = blockSchemas.length === 1 ? blockSchemas[0] : z.discriminatedUnion(
1841
+ "blockType",
1842
+ blockSchemas
1843
+ );
1844
+ let schema = z.array(union);
1845
+ if (props.field.min != null) schema = schema.min(props.field.min);
1846
+ if (props.field.max != null) schema = schema.max(props.field.max);
1847
+ return schema;
1848
+ }
1849
+ default:
1850
+ return z.any();
1851
+ }
1852
+ }
1853
+
1854
+ // src/formSchema/generateFormDefaultValues.ts
1855
+ function getFormDefaultValue(props) {
1856
+ switch (props.field.type) {
1857
+ case "text":
1858
+ return props.field.defaultValue ?? "";
1859
+ case "number":
1860
+ return props.field.defaultValue ?? 0;
1861
+ case "checkbox":
1862
+ return props.field.defaultValue ?? false;
1863
+ case "select":
1864
+ if (props.field.hasMany) {
1865
+ return props.field.defaultValue ? [props.field.defaultValue] : [];
1866
+ }
1867
+ return props.field.defaultValue ?? "";
1868
+ case "date":
1869
+ return props.field.defaultValue ?? 0;
1870
+ case "imageUrl":
1871
+ return props.field.defaultValue ?? "";
1872
+ case "relationship":
1873
+ return props.field.hasMany ? [] : "";
1874
+ case "upload":
1875
+ return props.field.hasMany ? [] : "";
1876
+ case "json":
1877
+ return {};
1878
+ case "richtext":
1879
+ return [];
1880
+ case "array":
1881
+ return [];
1882
+ case "blocks":
1883
+ return [];
1884
+ case "ui":
1885
+ return void 0;
1886
+ default:
1887
+ return void 0;
1888
+ }
1889
+ }
1890
+ function generateFormDefaultValues(props) {
1891
+ const result = {};
1892
+ for (const [fieldName, field] of Object.entries(props.fields)) {
1893
+ if (field.admin?.hidden) continue;
1894
+ if (field.type === "ui") continue;
1895
+ result[fieldName] = getFormDefaultValue({ field });
1896
+ }
1897
+ return result;
1898
+ }
1899
+
1900
+ // src/fields/ui/config.ts
1901
+ function ui(props) {
1902
+ return {
1903
+ type: "ui",
1904
+ label: props.label,
1905
+ description: props.description,
1906
+ admin: props.admin
1907
+ };
1908
+ }
1909
+
1910
+ // src/types/fields.ts
1911
+ var RESERVED_BLOCK_FIELD_NAMES = ["blockType", "blockName", "_key"];
1912
+
1913
+ // src/blocks/defineBlock.ts
1914
+ function defineBlock(props) {
1915
+ if (!props.slug || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(props.slug)) {
1916
+ throw new VexBlockValidationError(
1917
+ props.slug || "(empty)",
1918
+ `Invalid block slug "${props.slug}". Slugs must start with a letter and contain only letters, numbers, hyphens, and underscores.`
1919
+ );
1920
+ }
1921
+ for (const fieldName of Object.keys(props.fields)) {
1922
+ if (RESERVED_BLOCK_FIELD_NAMES.includes(fieldName)) {
1923
+ throw new VexBlockValidationError(
1924
+ props.slug,
1925
+ `Field name "${fieldName}" is reserved in block definitions. Reserved names: ${RESERVED_BLOCK_FIELD_NAMES.join(", ")}`
1926
+ );
1927
+ }
1928
+ }
1929
+ return {
1930
+ slug: props.slug,
1931
+ label: props.label,
1932
+ fields: props.fields,
1933
+ admin: props.admin,
1934
+ interfaceName: props.interfaceName
1935
+ };
1936
+ }
1937
+
1938
+ // src/typeGen/slugToInterfaceName.ts
1939
+ function slugToInterfaceName(props) {
1940
+ return props.slug.replace(/[-_]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1).toLowerCase()).join("");
1941
+ }
1942
+
1943
+ // src/typeGen/fieldToTypeString.ts
1944
+ function fieldToTypeString(props) {
1945
+ switch (props.field.type) {
1946
+ case "text":
1947
+ return "string";
1948
+ case "number":
1949
+ return "number";
1950
+ case "checkbox":
1951
+ return "boolean";
1952
+ case "date":
1953
+ return "number";
1954
+ case "imageUrl":
1955
+ return "string";
1956
+ case "json":
1957
+ return "Record<string, unknown>";
1958
+ case "richtext":
1959
+ return "any";
1960
+ case "ui":
1961
+ return "never";
1962
+ case "select": {
1963
+ const values = props.field.options.map((o) => o.value);
1964
+ if (values.length === 0) return "string";
1965
+ const union = values.map((v2) => `'${v2}'`).join(" | ");
1966
+ if (props.field.hasMany) {
1967
+ return `(${union})[]`;
1968
+ }
1969
+ return union;
1970
+ }
1971
+ case "relationship": {
1972
+ const idType = `Id<'${props.field.to}'>`;
1973
+ return props.field.hasMany ? `${idType}[]` : idType;
1974
+ }
1975
+ case "upload": {
1976
+ const idType = `Id<'${props.field.to}'>`;
1977
+ return props.field.hasMany ? `${idType}[]` : idType;
1978
+ }
1979
+ case "array": {
1980
+ const inner = fieldToTypeString({
1981
+ field: props.field.field,
1982
+ blockInterfaceNames: props.blockInterfaceNames
1983
+ });
1984
+ const needsParens = inner.includes("|");
1985
+ return needsParens ? `(${inner})[]` : `${inner}[]`;
1986
+ }
1987
+ case "blocks": {
1988
+ const names = props.field.blocks.map((b) => {
1989
+ if (props.blockInterfaceNames?.has(b.slug)) {
1990
+ return props.blockInterfaceNames.get(b.slug);
1991
+ }
1992
+ return b.interfaceName ?? slugToInterfaceName({ slug: b.slug });
1993
+ });
1994
+ if (names.length === 0) return "unknown[]";
1995
+ if (names.length === 1) return `${names[0]}[]`;
1996
+ return `(${names.join(" | ")})[]`;
1997
+ }
1998
+ default:
1999
+ return "unknown";
2000
+ }
2001
+ }
2002
+
2003
+ // src/typeGen/generateVexTypes.ts
2004
+ function generateVexTypes(props) {
2005
+ const config = props.config;
2006
+ const parts = [];
2007
+ const blocksBySlug = /* @__PURE__ */ new Map();
2008
+ const blockInterfaceNames = /* @__PURE__ */ new Map();
2009
+ function collectBlocks(fields) {
2010
+ for (const field of Object.values(fields)) {
2011
+ if (field.type === "blocks") {
2012
+ for (const block of field.blocks) {
2013
+ if (!blocksBySlug.has(block.slug)) {
2014
+ blocksBySlug.set(block.slug, block);
2015
+ collectBlocks(block.fields);
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ }
2021
+ for (const col of config.collections) {
2022
+ collectBlocks(col.fields);
2023
+ }
2024
+ if (config.media?.collections) {
2025
+ for (const col of config.media.collections) {
2026
+ collectBlocks(col.fields);
2027
+ }
2028
+ }
2029
+ for (const g of config.globals) {
2030
+ collectBlocks(g.fields);
2031
+ }
2032
+ for (const [slug, block] of blocksBySlug) {
2033
+ blockInterfaceNames.set(
2034
+ slug,
2035
+ block.interfaceName ?? slugToInterfaceName({ slug })
2036
+ );
2037
+ }
2038
+ const allNames = /* @__PURE__ */ new Map();
2039
+ function registerName(name, source) {
2040
+ if (allNames.has(name)) {
2041
+ throw new VexError(
2042
+ `Duplicate interface name "${name}" \u2014 used by ${allNames.get(name)} and ${source}. Set a unique \`interfaceName\` on one of them.`
2043
+ );
2044
+ }
2045
+ allNames.set(name, source);
2046
+ }
2047
+ const collectionNames = /* @__PURE__ */ new Map();
2048
+ for (const col of config.collections) {
2049
+ const name = col.interfaceName ?? slugToInterfaceName({ slug: col.slug });
2050
+ registerName(name, `collection "${col.slug}"`);
2051
+ collectionNames.set(col.slug, name);
2052
+ }
2053
+ if (config.media?.collections) {
2054
+ for (const col of config.media.collections) {
2055
+ const name = col.interfaceName ?? slugToInterfaceName({ slug: col.slug });
2056
+ registerName(name, `media collection "${col.slug}"`);
2057
+ collectionNames.set(col.slug, name);
2058
+ }
2059
+ }
2060
+ const authCollectionMap = new Map(
2061
+ config.auth.collections.map((c) => [c.slug, c])
2062
+ );
2063
+ const userCollectionSlugs = new Set(config.collections.map((c) => c.slug));
2064
+ const mediaSlugs = new Set(
2065
+ (config.media?.collections ?? []).map((c) => c.slug)
2066
+ );
2067
+ for (const authCol of config.auth.collections) {
2068
+ if (!userCollectionSlugs.has(authCol.slug) && !mediaSlugs.has(authCol.slug)) {
2069
+ const name = authCol.interfaceName ?? slugToInterfaceName({ slug: authCol.slug });
2070
+ registerName(name, `auth collection "${authCol.slug}"`);
2071
+ collectionNames.set(authCol.slug, name);
2072
+ }
2073
+ }
2074
+ const globalNames = /* @__PURE__ */ new Map();
2075
+ for (const g of config.globals) {
2076
+ const name = g.interfaceName ?? slugToInterfaceName({ slug: g.slug });
2077
+ registerName(name, `global "${g.slug}"`);
2078
+ globalNames.set(g.slug, name);
2079
+ }
2080
+ for (const [slug, name] of blockInterfaceNames) {
2081
+ registerName(name, `block "${slug}"`);
2082
+ }
2083
+ let needsIdImport = false;
2084
+ function checkForIdFields(fields) {
2085
+ for (const field of Object.values(fields)) {
2086
+ if (field.type === "relationship" || field.type === "upload") {
2087
+ needsIdImport = true;
2088
+ }
2089
+ }
2090
+ }
2091
+ for (const col of config.collections) {
2092
+ checkForIdFields(col.fields);
2093
+ }
2094
+ if (config.media?.collections) {
2095
+ for (const col of config.media.collections) {
2096
+ checkForIdFields(col.fields);
2097
+ }
2098
+ }
2099
+ for (const g of config.globals) {
2100
+ checkForIdFields(g.fields);
2101
+ }
2102
+ for (const block of blocksBySlug.values()) {
2103
+ checkForIdFields(block.fields);
2104
+ }
2105
+ if (config.collections.length > 0 || (config.media?.collections?.length ?? 0) > 0 || config.globals.length > 0) {
2106
+ needsIdImport = true;
2107
+ }
2108
+ parts.push("// \u26A0\uFE0F AUTO-GENERATED BY VEX CMS \u2014 DO NOT EDIT \u26A0\uFE0F");
2109
+ parts.push("");
2110
+ if (needsIdImport) {
2111
+ parts.push("import type { Id } from './_generated/dataModel';");
2112
+ parts.push("");
2113
+ }
2114
+ const sortedBlockSlugs = [...blocksBySlug.keys()].sort();
2115
+ for (const slug of sortedBlockSlugs) {
2116
+ const block = blocksBySlug.get(slug);
2117
+ const name = blockInterfaceNames.get(slug);
2118
+ parts.push(generateBlockInterface({ block, name, blockInterfaceNames }));
2119
+ parts.push("");
2120
+ }
2121
+ for (const col of config.collections) {
2122
+ const name = collectionNames.get(col.slug);
2123
+ const authCol = authCollectionMap.get(col.slug);
2124
+ let fields;
2125
+ if (authCol) {
2126
+ const merged = mergeAuthCollectionWithUserCollection({
2127
+ authCollection: authCol,
2128
+ userCollection: col
2129
+ });
2130
+ fields = merged.fields;
2131
+ } else {
2132
+ fields = col.fields;
2133
+ }
2134
+ const isVersioned = !!col.versions?.drafts;
2135
+ parts.push(
2136
+ generateCollectionInterface({
2137
+ name,
2138
+ slug: col.slug,
2139
+ fields,
2140
+ isVersioned,
2141
+ blockInterfaceNames
2142
+ })
2143
+ );
2144
+ parts.push("");
2145
+ }
2146
+ for (const authCol of config.auth.collections) {
2147
+ if (userCollectionSlugs.has(authCol.slug) || mediaSlugs.has(authCol.slug)) continue;
2148
+ const name = collectionNames.get(authCol.slug);
2149
+ parts.push(
2150
+ generateCollectionInterface({
2151
+ name,
2152
+ slug: authCol.slug,
2153
+ fields: authCol.fields,
2154
+ isVersioned: false,
2155
+ blockInterfaceNames
2156
+ })
2157
+ );
2158
+ parts.push("");
2159
+ }
2160
+ if (config.media?.collections) {
2161
+ for (const col of config.media.collections) {
2162
+ const name = collectionNames.get(col.slug);
2163
+ parts.push(
2164
+ generateMediaCollectionInterface({
2165
+ name,
2166
+ slug: col.slug,
2167
+ userFields: col.fields,
2168
+ blockInterfaceNames
2169
+ })
2170
+ );
2171
+ parts.push("");
2172
+ }
2173
+ }
2174
+ for (const g of config.globals) {
2175
+ const name = globalNames.get(g.slug);
2176
+ parts.push(
2177
+ generateGlobalInterface({
2178
+ name,
2179
+ slug: g.slug,
2180
+ fields: g.fields,
2181
+ blockInterfaceNames
2182
+ })
2183
+ );
2184
+ parts.push("");
2185
+ }
2186
+ if (collectionNames.size > 0) {
2187
+ const entries = [...collectionNames.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([slug, name]) => ` ${slug}: ${name};`).join("\n");
2188
+ parts.push(`export interface VexCollectionTypes {
2189
+ ${entries}
2190
+ }`);
2191
+ parts.push("");
2192
+ }
2193
+ if (globalNames.size > 0) {
2194
+ const entries = [...globalNames.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([slug, name]) => ` ${slug}: ${name};`).join("\n");
2195
+ parts.push(`export interface VexGlobalTypes {
2196
+ ${entries}
2197
+ }`);
2198
+ parts.push("");
2199
+ }
2200
+ return parts.join("\n");
2201
+ }
2202
+ function generateBlockInterface(props) {
2203
+ const lines = [];
2204
+ lines.push(`export interface ${props.name} {`);
2205
+ lines.push(` blockType: '${props.block.slug}';`);
2206
+ lines.push(` blockName?: string;`);
2207
+ lines.push(` _key: string;`);
2208
+ for (const [fieldName, field] of Object.entries(props.block.fields)) {
2209
+ const f = field;
2210
+ if (f.type === "ui") continue;
2211
+ const label = f.label;
2212
+ if (label) lines.push(` /** ${label} */`);
2213
+ const optional = f.required ? "" : "?";
2214
+ const typeStr = fieldToTypeString({
2215
+ field: f,
2216
+ blockInterfaceNames: props.blockInterfaceNames
2217
+ });
2218
+ lines.push(` ${fieldName}${optional}: ${typeStr};`);
2219
+ }
2220
+ lines.push("}");
2221
+ return lines.join("\n");
2222
+ }
2223
+ function generateCollectionInterface(props) {
2224
+ const lines = [];
2225
+ lines.push(`export interface ${props.name} {`);
2226
+ lines.push(` _id: Id<'${props.slug}'>;`);
2227
+ lines.push(` _creationTime: number;`);
2228
+ if (props.isVersioned) {
2229
+ lines.push(` vex_status?: 'draft' | 'published';`);
2230
+ lines.push(` vex_version?: number;`);
2231
+ lines.push(` vex_publishedAt?: number;`);
2232
+ }
2233
+ for (const [fieldName, field] of Object.entries(props.fields)) {
2234
+ const f = field;
2235
+ if (f.type === "ui") continue;
2236
+ const label = f.label;
2237
+ if (label) lines.push(` /** ${label} */`);
2238
+ const optional = f.required ? "" : "?";
2239
+ const typeStr = fieldToTypeString({
2240
+ field: f,
2241
+ blockInterfaceNames: props.blockInterfaceNames
2242
+ });
2243
+ lines.push(` ${fieldName}${optional}: ${typeStr};`);
2244
+ }
2245
+ lines.push("}");
2246
+ return lines.join("\n");
2247
+ }
2248
+ function generateMediaCollectionInterface(props) {
2249
+ const lines = [];
2250
+ lines.push(`export interface ${props.name} {`);
2251
+ lines.push(` _id: Id<'${props.slug}'>;`);
2252
+ lines.push(` _creationTime: number;`);
2253
+ lines.push(` storageId: string;`);
2254
+ lines.push(` filename: string;`);
2255
+ lines.push(` mimeType: string;`);
2256
+ lines.push(` size: number;`);
2257
+ lines.push(` url?: string;`);
2258
+ lines.push(` width?: number;`);
2259
+ lines.push(` height?: number;`);
2260
+ const lockedSet = /* @__PURE__ */ new Set([...LOCKED_MEDIA_FIELDS, ...OVERRIDABLE_MEDIA_FIELDS]);
2261
+ for (const [fieldName, field] of Object.entries(props.userFields)) {
2262
+ if (lockedSet.has(fieldName)) continue;
2263
+ const f = field;
2264
+ if (f.type === "ui") continue;
2265
+ const label = f.label;
2266
+ if (label) lines.push(` /** ${label} */`);
2267
+ const optional = f.required ? "" : "?";
2268
+ const typeStr = fieldToTypeString({
2269
+ field: f,
2270
+ blockInterfaceNames: props.blockInterfaceNames
2271
+ });
2272
+ lines.push(` ${fieldName}${optional}: ${typeStr};`);
2273
+ }
2274
+ lines.push("}");
2275
+ return lines.join("\n");
2276
+ }
2277
+ function generateGlobalInterface(props) {
2278
+ const lines = [];
2279
+ lines.push(`export interface ${props.name} {`);
2280
+ lines.push(` _id: Id<'vex_globals'>;`);
2281
+ lines.push(` _creationTime: number;`);
2282
+ lines.push(` vexGlobalSlug: '${props.slug}';`);
2283
+ for (const [fieldName, field] of Object.entries(props.fields)) {
2284
+ const f = field;
2285
+ if (f.type === "ui") continue;
2286
+ const label = f.label;
2287
+ if (label) lines.push(` /** ${label} */`);
2288
+ const optional = f.required ? "" : "?";
2289
+ const typeStr = fieldToTypeString({
2290
+ field: f,
2291
+ blockInterfaceNames: props.blockInterfaceNames
2292
+ });
2293
+ lines.push(` ${fieldName}${optional}: ${typeStr};`);
2294
+ }
2295
+ lines.push("}");
2296
+ return lines.join("\n");
2297
+ }
2298
+
2299
+ // src/versioning/constants.ts
2300
+ var VERSION_SYSTEM_FIELDS = [
2301
+ "vex_status",
2302
+ "vex_version",
2303
+ "vex_publishedAt"
2304
+ ];
2305
+ var ALL_SYSTEM_FIELDS = /* @__PURE__ */ new Set([
2306
+ "_id",
2307
+ "_creationTime",
2308
+ ...VERSION_SYSTEM_FIELDS
2309
+ ]);
2310
+ var DEFAULT_MAX_VERSIONS_PER_DOC = 100;
2311
+ var DEFAULT_AUTOSAVE_INTERVAL = 2e3;
2312
+
2313
+ // src/versioning/extractUserFields.ts
2314
+ function extractUserFields(props) {
2315
+ const result = {};
2316
+ for (const [key, value] of Object.entries(props.document)) {
2317
+ if (!ALL_SYSTEM_FIELDS.has(key)) {
2318
+ result[key] = value;
2319
+ }
2320
+ }
2321
+ return result;
2322
+ }
2323
+
2324
+ // src/livePreview/resolvePreviewURL.ts
2325
+ function resolvePreviewURL(props) {
2326
+ if (typeof props.config.url === "string") {
2327
+ return props.config.url;
2328
+ }
2329
+ try {
2330
+ const result = props.config.url(props.doc);
2331
+ if (!result) {
2332
+ throw new Error(
2333
+ `Live preview URL resolved to empty string for document ${props.doc._id}`
2334
+ );
2335
+ }
2336
+ return result;
2337
+ } catch (error) {
2338
+ if (props.fallbackURL !== void 0) {
2339
+ return props.fallbackURL;
2340
+ }
2341
+ throw error;
2342
+ }
2343
+ }
2344
+
2345
+ // src/livePreview/shouldReloadURL.ts
2346
+ function shouldReloadURL(props) {
2347
+ if (props.config.reloadOnFields === void 0) {
2348
+ return true;
2349
+ }
2350
+ if (props.config.reloadOnFields.length === 0) {
2351
+ return false;
2352
+ }
2353
+ return props.changedFields.some(
2354
+ (field) => props.config.reloadOnFields.includes(field)
2355
+ );
2356
+ }
2357
+
2358
+ // src/livePreview/constants.ts
2359
+ var DEFAULT_BREAKPOINTS = [
2360
+ { label: "Mobile", width: 375, height: 667, icon: "smartphone" },
2361
+ { label: "Tablet", width: 768, height: 1024, icon: "tablet" },
2362
+ { label: "Laptop", width: 1280, height: 800, icon: "laptop" },
2363
+ { label: "Desktop", width: 1920, height: 1080, icon: "monitor" }
2364
+ ];
2365
+ var PREVIEW_SNAPSHOT_DEBOUNCE_MS = 500;
2366
+
2367
+ // src/convex/previewSnapshot.ts
2368
+ async function upsertPreviewSnapshot(props) {
2369
+ const existing = await props.ctx.db.query("vex_versions").withIndex(
2370
+ "by_document_status",
2371
+ (q) => q.eq("collection", props.collection).eq("documentId", props.documentId).eq("status", "previewSnapshot")
2372
+ ).first();
2373
+ if (existing) {
2374
+ await props.ctx.db.patch(existing._id, {
2375
+ snapshot: props.snapshot,
2376
+ createdAt: Date.now()
2377
+ });
2378
+ } else {
2379
+ await props.ctx.db.insert("vex_versions", {
2380
+ collection: props.collection,
2381
+ documentId: props.documentId,
2382
+ version: 0,
2383
+ status: "previewSnapshot",
2384
+ snapshot: props.snapshot,
2385
+ createdAt: Date.now(),
2386
+ createdBy: void 0,
2387
+ isAutosave: false,
2388
+ restoredFrom: void 0
2389
+ });
2390
+ }
2391
+ }
2392
+ async function deletePreviewSnapshot(props) {
2393
+ const entries = await props.ctx.db.query("vex_versions").withIndex(
2394
+ "by_document_status",
2395
+ (q) => q.eq("collection", props.collection).eq("documentId", props.documentId).eq("status", "previewSnapshot")
2396
+ ).collect();
2397
+ for (const entry of entries) {
2398
+ await props.ctx.db.delete(entry._id);
2399
+ }
2400
+ }
2401
+ async function getPreviewSnapshot(props) {
2402
+ const entry = await props.ctx.db.query("vex_versions").withIndex(
2403
+ "by_document_status",
2404
+ (q) => q.eq("collection", props.collection).eq("documentId", props.documentId).eq("status", "previewSnapshot")
2405
+ ).first();
2406
+ if (!entry) return null;
2407
+ return entry.snapshot;
2408
+ }
2409
+
2410
+ // src/convex/vexQuery.ts
2411
+ import {
2412
+ queryGeneric
2413
+ } from "convex/server";
2414
+ import { v } from "convex/values";
2415
+ function vexQuery(props) {
2416
+ const mergedArgs = {
2417
+ ...props.args,
2418
+ _vexDrafts: v.optional(v.union(v.literal("snapshot"), v.boolean()))
2419
+ };
2420
+ return queryGeneric({
2421
+ args: mergedArgs,
2422
+ handler: async (ctx, args) => {
2423
+ const { _vexDrafts, ...userArgs } = args;
2424
+ const drafts = _vexDrafts !== void 0 ? _vexDrafts : "snapshot";
2425
+ const vexCtx = Object.assign(Object.create(Object.getPrototypeOf(ctx)), ctx, {
2426
+ drafts
2427
+ });
2428
+ return props.handler(vexCtx, userArgs);
2429
+ }
2430
+ });
2431
+ }
2432
+
2433
+ // src/convex/model/collections.ts
2434
+ import { ConvexError } from "convex/values";
2435
+ async function resolveStorageUrl(ctx, doc) {
2436
+ if (doc?.storageId && (!doc.url || doc.url === "")) {
2437
+ const url = await ctx.storage.getUrl(doc.storageId);
2438
+ if (url) return { ...doc, url };
2439
+ }
2440
+ return doc;
2441
+ }
2442
+ async function listDocuments(props) {
2443
+ const { args, ctx } = props;
2444
+ const q = args.order === "desc" ? ctx.db.query(args.collectionSlug).order("desc") : ctx.db.query(args.collectionSlug);
2445
+ const result = await q.paginate(args.paginationOpts);
2446
+ const resolvedPage = await Promise.all(
2447
+ result.page.map((doc) => resolveStorageUrl(ctx, doc))
2448
+ );
2449
+ return { ...result, page: resolvedPage };
2450
+ }
2451
+ async function getDocument(props) {
2452
+ const doc = await props.ctx.db.get(props.args.documentId);
2453
+ if (!doc) return null;
2454
+ const resolved = await resolveStorageUrl(props.ctx, doc);
2455
+ if (props.args.preview) {
2456
+ const snapshot = await getPreviewSnapshot({
2457
+ ctx: props.ctx,
2458
+ collection: props.args.collectionSlug,
2459
+ documentId: props.args.documentId
2460
+ });
2461
+ if (snapshot) {
2462
+ return { ...resolved, ...snapshot };
2463
+ }
2464
+ }
2465
+ return resolved;
2466
+ }
2467
+ async function updateDocument(props) {
2468
+ const f = { ...props.args.fields };
2469
+ if (f.storageId && f.url === "") {
2470
+ const url = await props.ctx.storage.getUrl(f.storageId);
2471
+ if (url) f.url = url;
2472
+ }
2473
+ const schema = generateFormSchema({
2474
+ fields: props.args.collectionFields
2475
+ }).partial();
2476
+ const result = schema.safeParse(f);
2477
+ if (!result.success) {
2478
+ throw new ConvexError({
2479
+ message: "Validation failed",
2480
+ errors: result.error.flatten()
2481
+ });
2482
+ }
2483
+ await props.ctx.db.patch(props.args.documentId, result.data);
2484
+ return props.args.documentId;
2485
+ }
2486
+ async function createDocument(props) {
2487
+ if (props.args.kind === "global") {
2488
+ const existing = await props.ctx.db.query(props.args.collectionSlug).first();
2489
+ if (existing) {
2490
+ throw new ConvexError(
2491
+ `Global "${props.args.collectionSlug}" already exists. Globals can only have one document.`
2492
+ );
2493
+ }
2494
+ }
2495
+ const schema = generateFormSchema({
2496
+ fields: props.args.collectionFields
2497
+ });
2498
+ const result = schema.safeParse(props.args.fields);
2499
+ if (!result.success) {
2500
+ throw new ConvexError({
2501
+ message: "Validation failed",
2502
+ errors: result.error.flatten()
2503
+ });
2504
+ }
2505
+ const id = await props.ctx.db.insert(props.args.collectionSlug, result.data);
2506
+ return id;
2507
+ }
2508
+ async function deleteDocument(props) {
2509
+ if (props.args.kind === "global") {
2510
+ const existing = await props.ctx.db.get(props.args.documentId);
2511
+ if (!existing) {
2512
+ throw new ConvexError(
2513
+ `Global "${props.args.collectionSlug}" document not found. Cannot delete a non-existent global.`
2514
+ );
2515
+ }
2516
+ }
2517
+ await props.ctx.db.delete(props.args.documentId);
2518
+ }
2519
+ async function searchDocuments(props) {
2520
+ const { args, ctx } = props;
2521
+ const docs = await ctx.db.query(args.collectionSlug).withSearchIndex(args.searchIndexName, (q) => q.search(args.searchField, args.query)).take(50);
2522
+ return Promise.all(docs.map((doc) => resolveStorageUrl(ctx, doc)));
2523
+ }
2524
+
2525
+ // src/valueTypes/generateCollectionQueries.ts
2526
+ var GENERATED_HEADER = "// \u26A0\uFE0F AUTO-GENERATED BY VEX CMS \u2014 DO NOT EDIT \u26A0\uFE0F";
2527
+ function generateCollectionQueries(props) {
2528
+ const { config, imports } = props;
2529
+ const result = {};
2530
+ const slugs = [];
2531
+ for (const collection of config.collections) {
2532
+ const { apiFile, modelFile } = generateCollectionPair({
2533
+ collection,
2534
+ isMedia: false,
2535
+ imports
2536
+ });
2537
+ result[`api/${collection.slug}.ts`] = apiFile;
2538
+ result[`model/api/${collection.slug}.ts`] = modelFile;
2539
+ slugs.push(collection.slug);
2540
+ }
2541
+ if (config.media?.collections) {
2542
+ for (const collection of config.media.collections) {
2543
+ const { apiFile, modelFile } = generateCollectionPair({
2544
+ collection,
2545
+ isMedia: true,
2546
+ imports
2547
+ });
2548
+ result[`api/${collection.slug}.ts`] = apiFile;
2549
+ result[`model/api/${collection.slug}.ts`] = modelFile;
2550
+ slugs.push(collection.slug);
2551
+ }
2552
+ }
2553
+ if (config.auth?.collections) {
2554
+ for (const collection of config.auth.collections) {
2555
+ if (!collection.generateApi) continue;
2556
+ if (slugs.includes(collection.slug)) continue;
2557
+ const { apiFile, modelFile } = generateCollectionPair({
2558
+ collection,
2559
+ isMedia: false,
2560
+ imports
2561
+ });
2562
+ result[`api/${collection.slug}.ts`] = apiFile;
2563
+ result[`model/api/${collection.slug}.ts`] = modelFile;
2564
+ slugs.push(collection.slug);
2565
+ }
2566
+ }
2567
+ result["api/index.ts"] = generateIndexFile({ slugs });
2568
+ return result;
2569
+ }
2570
+ function generateCollectionPair(props) {
2571
+ const { collection, isMedia, imports } = props;
2572
+ const slug = collection.slug;
2573
+ const tableName = collection.tableName ?? collection.slug;
2574
+ const firstSearchIndex = collection.searchIndexes?.[0] ?? null;
2575
+ const modelFile = generateModelFile({ slug, tableName, isMedia, firstSearchIndex, imports });
2576
+ const apiFile = generateApiFile({ slug, tableName, isMedia, firstSearchIndex, imports });
2577
+ return { apiFile, modelFile };
2578
+ }
2579
+ function generateModelFile(props) {
2580
+ const { tableName, isMedia, firstSearchIndex, imports } = props;
2581
+ const parts = [];
2582
+ const coreImports = ["getPreviewSnapshot"];
2583
+ if (!isMedia) {
2584
+ coreImports.push("generateFormSchema");
2585
+ }
2586
+ const coreTypeImports = ["CollectionKind"];
2587
+ if (!isMedia) {
2588
+ coreTypeImports.push("VexField");
2589
+ }
2590
+ const coreTypeImportLine = coreTypeImports.length > 0 ? `
2591
+ import type { ${coreTypeImports.join(", ")} } from "@vexcms/core"` : "";
2592
+ const convexTypeImports = isMedia ? "" : `
2593
+ import type { WithoutSystemFields } from "convex/server"`;
2594
+ parts.push(`${GENERATED_HEADER}
2595
+ import type { Doc, Id } from "${imports.generatedDirFromModel}/dataModel"
2596
+ import type { QueryCtx, MutationCtx } from "${imports.generatedDirFromModel}/server"${convexTypeImports}
2597
+ import { ConvexError } from "convex/values"
2598
+ import { ${coreImports.join(", ")} } from "@vexcms/core"${coreTypeImportLine}`);
2599
+ parts.push(buildModelGetDocument({ tableName }));
2600
+ parts.push(buildModelListDocuments({ tableName }));
2601
+ if (!isMedia) {
2602
+ parts.push(buildModelCreateDocument({ tableName }));
2603
+ }
2604
+ if (!isMedia) {
2605
+ parts.push(buildModelUpdateDocument({ tableName }));
2606
+ }
2607
+ parts.push(buildModelDeleteDocument({ tableName }));
2608
+ if (firstSearchIndex) {
2609
+ parts.push(buildModelSearchDocuments({
2610
+ tableName,
2611
+ searchIndexName: firstSearchIndex.name,
2612
+ searchField: firstSearchIndex.searchField
2613
+ }));
2614
+ }
2615
+ return parts.join("\n\n") + "\n";
2616
+ }
2617
+ function buildModelGetDocument(props) {
2618
+ const { tableName } = props;
2619
+ return `export async function getDocument(props: {
2620
+ ctx: QueryCtx
2621
+ documentId: Id<"${tableName}">
2622
+ preview?: boolean
2623
+ }): Promise<Doc<"${tableName}"> | null> {
2624
+ const doc = await props.ctx.db.get(props.documentId)
2625
+ if (!doc) return null
2626
+
2627
+ if (props.preview) {
2628
+ const snapshot = await getPreviewSnapshot({
2629
+ ctx: props.ctx,
2630
+ collection: "${tableName}",
2631
+ documentId: props.documentId,
2632
+ })
2633
+ if (snapshot) {
2634
+ return { ...doc, ...snapshot } as Doc<"${tableName}">
2635
+ }
2636
+ }
2637
+
2638
+ return doc
2639
+ }`;
2640
+ }
2641
+ function buildModelListDocuments(props) {
2642
+ const { tableName } = props;
2643
+ return `export async function listDocuments(props: {
2644
+ ctx: QueryCtx
2645
+ paginationOpts: { numItems: number; cursor: string | null }
2646
+ order?: "asc" | "desc"
2647
+ }) {
2648
+ const q = props.order === "desc"
2649
+ ? props.ctx.db.query("${tableName}").order("desc")
2650
+ : props.ctx.db.query("${tableName}")
2651
+ return await q.paginate(props.paginationOpts)
2652
+ }`;
2653
+ }
2654
+ function buildModelCreateDocument(props) {
2655
+ const { tableName } = props;
2656
+ return `export async function createDocument(props: {
2657
+ collectionFields: Record<string, VexField>
2658
+ ctx: MutationCtx
2659
+ fields: unknown
2660
+ kind: CollectionKind
2661
+ }): Promise<Id<"${tableName}">> {
2662
+ if (props.kind === "global") {
2663
+ const existing = await props.ctx.db.query("${tableName}").first()
2664
+ if (existing) {
2665
+ throw new ConvexError(\`Global "${tableName}" already exists. Globals can only have one document.\`)
2666
+ }
2667
+ }
2668
+
2669
+ const schema = generateFormSchema({ fields: props.collectionFields })
2670
+ const parsed = schema.safeParse(props.fields)
2671
+ if (!parsed.success) {
2672
+ throw new ConvexError({ message: "Validation failed", errors: parsed.error.flatten() })
2673
+ }
2674
+
2675
+ const data = { ...parsed.data }
2676
+ data.vex_status ??= "published"
2677
+ return await props.ctx.db.insert("${tableName}", data as WithoutSystemFields<Doc<"${tableName}">>)
2678
+ }`;
2679
+ }
2680
+ function buildModelUpdateDocument(props) {
2681
+ const { tableName } = props;
2682
+ return `export async function updateDocument(props: {
2683
+ collectionFields: Record<string, VexField>
2684
+ ctx: MutationCtx
2685
+ documentId: Id<"${tableName}">
2686
+ fields: unknown
2687
+ }): Promise<Id<"${tableName}">> {
2688
+ const schema = generateFormSchema({ fields: props.collectionFields }).partial()
2689
+ const parsed = schema.safeParse(props.fields)
2690
+ if (!parsed.success) {
2691
+ throw new ConvexError({ message: "Validation failed", errors: parsed.error.flatten() })
2692
+ }
2693
+
2694
+ await props.ctx.db.patch(props.documentId, parsed.data as Partial<Doc<"${tableName}">>)
2695
+ return props.documentId
2696
+ }`;
2697
+ }
2698
+ function buildModelDeleteDocument(props) {
2699
+ const { tableName } = props;
2700
+ return `export async function deleteDocument(props: {
2701
+ ctx: MutationCtx
2702
+ documentId: Id<"${tableName}">
2703
+ kind: CollectionKind
2704
+ }): Promise<void> {
2705
+ if (props.kind === "global") {
2706
+ const existing = await props.ctx.db.get(props.documentId)
2707
+ if (!existing) {
2708
+ throw new ConvexError(\`Global "${tableName}" document not found. Cannot delete a non-existent global.\`)
2709
+ }
2710
+ }
2711
+
2712
+ await props.ctx.db.delete(props.documentId)
2713
+ }`;
2714
+ }
2715
+ function buildModelSearchDocuments(props) {
2716
+ const { tableName, searchIndexName, searchField } = props;
2717
+ return `export async function searchDocuments(props: {
2718
+ ctx: QueryCtx
2719
+ query: string
2720
+ }): Promise<Doc<"${tableName}">[]> {
2721
+ return await props.ctx.db.query("${tableName}")
2722
+ .withSearchIndex("${searchIndexName}", (q) => q.search("${searchField}", props.query))
2723
+ .take(50)
2724
+ }`;
2725
+ }
2726
+ function generateApiFile(props) {
2727
+ const { slug, tableName, isMedia, firstSearchIndex, imports } = props;
2728
+ const parts = [];
2729
+ const modelFns = [
2730
+ "getDocument",
2731
+ "listDocuments",
2732
+ ...isMedia ? [] : ["createDocument", "updateDocument"],
2733
+ "deleteDocument",
2734
+ ...firstSearchIndex ? ["searchDocuments"] : []
2735
+ ];
2736
+ parts.push(`${GENERATED_HEADER}
2737
+ import { v } from "convex/values"
2738
+ import { paginationOptsValidator } from "convex/server"
2739
+ import { ConvexError } from "convex/values"
2740
+ import { query, mutation } from "${imports.generatedDirFromApi}/server"
2741
+ import type { QueryCtx, MutationCtx } from "${imports.generatedDirFromApi}/server"
2742
+ import { hasPermission, findCollectionBySlug } from "@vexcms/core"
2743
+ import { getUser } from "${imports.authFromApi}"
2744
+ import vexConfig from "${imports.vexConfigFromApi}"
2745
+ import { ${modelFns.join(", ")} } from "../model/api/${slug}"`);
2746
+ parts.push(`const SLUG = "${slug}" as const
2747
+
2748
+ async function requireAuth(ctx: QueryCtx | MutationCtx) {
2749
+ const auth = await getUser(ctx)
2750
+ if (!auth) throw new ConvexError("Not authenticated")
2751
+ return auth
2752
+ }`);
2753
+ parts.push(buildApiGetDocument({ tableName }));
2754
+ parts.push(buildApiListDocuments({ tableName }));
2755
+ if (!isMedia) {
2756
+ parts.push(buildApiCreateDocument({ slug, tableName }));
2757
+ }
2758
+ if (!isMedia) {
2759
+ parts.push(buildApiUpdateDocument({ slug, tableName }));
2760
+ }
2761
+ parts.push(buildApiDeleteDocument({ tableName }));
2762
+ if (firstSearchIndex) {
2763
+ parts.push(buildApiSearchDocuments());
2764
+ }
2765
+ return parts.join("\n\n") + "\n";
2766
+ }
2767
+ function buildApiGetDocument(props) {
2768
+ const { tableName } = props;
2769
+ return `export const get = query({
2770
+ args: {
2771
+ id: v.id("${tableName}"),
2772
+ _vexDrafts: v.optional(v.union(v.literal("snapshot"), v.boolean())),
2773
+ },
2774
+ handler: async (ctx, args) => {
2775
+ const preview = (args._vexDrafts ?? "snapshot") === "snapshot"
2776
+ const doc = await getDocument({
2777
+ ctx,
2778
+ documentId: args.id,
2779
+ preview,
2780
+ })
2781
+ if (!doc) return null
2782
+ const auth = await getUser(ctx)
2783
+ if (auth) {
2784
+ const allowed = hasPermission({
2785
+ access: vexConfig.access,
2786
+ user: auth.user,
2787
+ userRoles: auth.roles,
2788
+ resource: SLUG,
2789
+ action: "read",
2790
+ data: doc,
2791
+ })
2792
+ if (!allowed) return null
2793
+ }
2794
+ return doc
2795
+ },
2796
+ })`;
2797
+ }
2798
+ function buildApiListDocuments(_props) {
2799
+ return `export const list = query({
2800
+ args: {
2801
+ paginationOpts: paginationOptsValidator,
2802
+ order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
2803
+ },
2804
+ handler: async (ctx, args) => {
2805
+ const result = await listDocuments({
2806
+ ctx,
2807
+ paginationOpts: args.paginationOpts,
2808
+ order: args.order,
2809
+ })
2810
+ const auth = await getUser(ctx)
2811
+ if (!auth) return result
2812
+ const filteredPage = result.page.filter((doc) =>
2813
+ hasPermission({
2814
+ access: vexConfig.access,
2815
+ user: auth.user,
2816
+ userRoles: auth.roles,
2817
+ resource: SLUG,
2818
+ action: "read",
2819
+ data: doc,
2820
+ }) === true,
2821
+ )
2822
+ return { ...result, page: filteredPage }
2823
+ },
2824
+ })`;
2825
+ }
2826
+ function buildApiCreateDocument(_props) {
2827
+ return `export const create = mutation({
2828
+ args: { fields: v.any() },
2829
+ handler: async (ctx, args) => {
2830
+ const { user, roles } = await requireAuth(ctx)
2831
+ hasPermission({
2832
+ access: vexConfig.access,
2833
+ user,
2834
+ userRoles: roles,
2835
+ resource: SLUG,
2836
+ action: "create",
2837
+ throwOnDenied: true,
2838
+ })
2839
+ const collection = findCollectionBySlug({ slug: SLUG, config: vexConfig })
2840
+ if (!collection) throw new ConvexError(\`Collection "\${SLUG}" not found in vex config\`)
2841
+ return createDocument({
2842
+ collectionFields: collection.fields,
2843
+ ctx,
2844
+ fields: args.fields as unknown,
2845
+ kind: "collection",
2846
+ })
2847
+ },
2848
+ })`;
2849
+ }
2850
+ function buildApiUpdateDocument(props) {
2851
+ const { tableName } = props;
2852
+ return `export const update = mutation({
2853
+ args: { id: v.id("${tableName}"), fields: v.any() },
2854
+ handler: async (ctx, args) => {
2855
+ const { user, roles } = await requireAuth(ctx)
2856
+ hasPermission({
2857
+ access: vexConfig.access,
2858
+ user,
2859
+ userRoles: roles,
2860
+ resource: SLUG,
2861
+ action: "update",
2862
+ throwOnDenied: true,
2863
+ })
2864
+ const collection = findCollectionBySlug({ slug: SLUG, config: vexConfig })
2865
+ if (!collection) throw new ConvexError(\`Collection "\${SLUG}" not found in vex config\`)
2866
+ return updateDocument({
2867
+ collectionFields: collection.fields,
2868
+ ctx,
2869
+ documentId: args.id,
2870
+ fields: args.fields,
2871
+ })
2872
+ },
2873
+ })`;
2874
+ }
2875
+ function buildApiDeleteDocument(props) {
2876
+ const { tableName } = props;
2877
+ return `export const remove = mutation({
2878
+ args: { id: v.id("${tableName}") },
2879
+ handler: async (ctx, args) => {
2880
+ const { user, roles } = await requireAuth(ctx)
2881
+ hasPermission({
2882
+ access: vexConfig.access,
2883
+ user,
2884
+ userRoles: roles,
2885
+ resource: SLUG,
2886
+ action: "delete",
2887
+ throwOnDenied: true,
2888
+ })
2889
+ await deleteDocument({
2890
+ ctx,
2891
+ documentId: args.id,
2892
+ kind: "collection",
2893
+ })
2894
+ },
2895
+ })`;
2896
+ }
2897
+ function buildApiSearchDocuments() {
2898
+ return `export const search = query({
2899
+ args: { query: v.string() },
2900
+ handler: async (ctx, args) => {
2901
+ const results = await searchDocuments({
2902
+ ctx,
2903
+ query: args.query,
2904
+ })
2905
+ const auth = await getUser(ctx)
2906
+ if (!auth) return results
2907
+ return results.filter((doc) =>
2908
+ hasPermission({
2909
+ access: vexConfig.access,
2910
+ user: auth.user,
2911
+ userRoles: auth.roles,
2912
+ resource: SLUG,
2913
+ action: "read",
2914
+ data: doc,
2915
+ }) === true,
2916
+ )
2917
+ },
2918
+ })`;
2919
+ }
2920
+ function generateIndexFile(props) {
2921
+ const sorted = [...props.slugs].sort();
2922
+ if (sorted.length === 0) {
2923
+ return GENERATED_HEADER + "\n";
2924
+ }
2925
+ const exports = sorted.map((slug) => `export * as ${slug} from "./${slug}"`).join("\n");
2926
+ return `${GENERATED_HEADER}
2927
+ ${exports}
2928
+ `;
2929
+ }
2930
+
2931
+ // src/migrations/diffSchema.ts
2932
+ function parseTables(schema) {
2933
+ const tables = /* @__PURE__ */ new Map();
2934
+ if (!schema.trim()) return tables;
2935
+ const tableRegex = /export\s+const\s+(\w+)\s*=\s*defineTable\(\{([\s\S]*?)\}\)/g;
2936
+ let match;
2937
+ while ((match = tableRegex.exec(schema)) !== null) {
2938
+ const name = match[1];
2939
+ const body = match[2];
2940
+ const fields = /* @__PURE__ */ new Map();
2941
+ const fieldRegex = /^\s+(\w+):\s+(v\..+?),?\s*$/gm;
2942
+ let fieldMatch;
2943
+ while ((fieldMatch = fieldRegex.exec(body)) !== null) {
2944
+ const fieldName = fieldMatch[1];
2945
+ const valueType = fieldMatch[2].replace(/,\s*$/, "");
2946
+ const isOptional = valueType.startsWith("v.optional(");
2947
+ fields.set(fieldName, { valueType, isOptional });
2948
+ }
2949
+ tables.set(name, { name, fields });
2950
+ }
2951
+ return tables;
2952
+ }
2953
+ function diffSchema(oldSchema, newSchema) {
2954
+ const oldTables = parseTables(oldSchema);
2955
+ const newTables = parseTables(newSchema);
2956
+ const addedRequired = [];
2957
+ const addedOptional = [];
2958
+ const newRequired = [];
2959
+ const removedFields = [];
2960
+ for (const [tableName, newTable] of newTables) {
2961
+ const oldTable = oldTables.get(tableName);
2962
+ for (const [fieldName, newField] of newTable.fields) {
2963
+ const info = {
2964
+ table: tableName,
2965
+ field: fieldName,
2966
+ valueType: newField.valueType,
2967
+ isOptional: newField.isOptional
2968
+ };
2969
+ if (!oldTable) {
2970
+ if (newField.isOptional) {
2971
+ addedOptional.push(info);
2972
+ } else {
2973
+ addedRequired.push(info);
2974
+ }
2975
+ } else {
2976
+ const oldField = oldTable.fields.get(fieldName);
2977
+ if (!oldField) {
2978
+ if (newField.isOptional) {
2979
+ addedOptional.push(info);
2980
+ } else {
2981
+ addedRequired.push(info);
2982
+ }
2983
+ } else if (oldField.isOptional && !newField.isOptional) {
2984
+ newRequired.push(info);
2985
+ }
2986
+ }
2987
+ }
2988
+ }
2989
+ for (const [tableName, oldTable] of oldTables) {
2990
+ const newTable = newTables.get(tableName);
2991
+ if (!newTable) continue;
2992
+ for (const [fieldName, oldField] of oldTable.fields) {
2993
+ if (!newTable.fields.has(fieldName)) {
2994
+ removedFields.push({
2995
+ table: tableName,
2996
+ field: fieldName,
2997
+ valueType: oldField.valueType,
2998
+ wasOptional: oldField.isOptional
2999
+ });
3000
+ }
3001
+ }
3002
+ }
3003
+ return {
3004
+ addedRequired,
3005
+ addedOptional,
3006
+ newRequired,
3007
+ removedFields,
3008
+ needsMigration: [...addedRequired, ...addedOptional, ...newRequired]
3009
+ };
3010
+ }
3011
+ function makeFieldsOptional(schema, fields) {
3012
+ let result = schema;
3013
+ for (const field of fields) {
3014
+ if (field.isOptional) continue;
3015
+ const escaped = field.field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3016
+ const pattern = new RegExp(
3017
+ `(\\s+${escaped}:\\s+)(${field.valueType.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(,?)`
3018
+ );
3019
+ result = result.replace(pattern, `$1v.optional($2)$3`);
3020
+ }
3021
+ return result;
3022
+ }
3023
+ function addRemovedFieldsAsOptional(schema, fields) {
3024
+ let result = schema;
3025
+ const byTable = /* @__PURE__ */ new Map();
3026
+ for (const f of fields) {
3027
+ const list = byTable.get(f.table) ?? [];
3028
+ list.push(f);
3029
+ byTable.set(f.table, list);
3030
+ }
3031
+ for (const [tableName, tableFields] of byTable) {
3032
+ const tablePattern = new RegExp(
3033
+ `(export\\s+const\\s+${tableName}\\s*=\\s*defineTable\\(\\{[\\s\\S]*?)(\\}\\))`
3034
+ );
3035
+ const tableMatch = result.match(tablePattern);
3036
+ if (!tableMatch) continue;
3037
+ const extraLines = tableFields.map((f) => {
3038
+ const optionalType = f.wasOptional ? f.valueType : `v.optional(${f.valueType})`;
3039
+ return ` ${f.field}: ${optionalType},`;
3040
+ });
3041
+ result = result.replace(
3042
+ tablePattern,
3043
+ `$1${extraLines.join("\n")}
3044
+ $2`
3045
+ );
3046
+ }
3047
+ return result;
3048
+ }
3049
+
3050
+ // src/migrations/planMigration.ts
3051
+ function planMigration(props) {
3052
+ const { diff, config } = props;
3053
+ if (diff.needsMigration.length === 0) return [];
3054
+ const tableFieldsMap = /* @__PURE__ */ new Map();
3055
+ for (const collection of config.collections) {
3056
+ tableFieldsMap.set(collection.slug, collection.fields);
3057
+ }
3058
+ for (const global of config.globals) {
3059
+ tableFieldsMap.set(global.slug, global.fields);
3060
+ }
3061
+ const ops = [];
3062
+ for (const fieldInfo of diff.needsMigration) {
3063
+ const collectionFields = tableFieldsMap.get(fieldInfo.table);
3064
+ if (!collectionFields) {
3065
+ continue;
3066
+ }
3067
+ const field = collectionFields[fieldInfo.field];
3068
+ if (!field) {
3069
+ continue;
3070
+ }
3071
+ if (!field.required) {
3072
+ continue;
3073
+ }
3074
+ const defaultValue = field.defaultValue;
3075
+ if (defaultValue === void 0) {
3076
+ continue;
3077
+ }
3078
+ ops.push({
3079
+ table: fieldInfo.table,
3080
+ field: fieldInfo.field,
3081
+ defaultValue
3082
+ });
3083
+ }
3084
+ return ops;
3085
+ }
3086
+ export {
3087
+ ALL_SYSTEM_FIELDS,
3088
+ DEFAULT_AUTOSAVE_INTERVAL,
3089
+ DEFAULT_BREAKPOINTS,
3090
+ DEFAULT_MAX_VERSIONS_PER_DOC,
3091
+ GENERATED_HEADER,
3092
+ LOCKED_MEDIA_FIELDS,
3093
+ OVERRIDABLE_MEDIA_FIELDS,
3094
+ PREVIEW_SNAPSHOT_DEBOUNCE_MS,
3095
+ VERSION_SYSTEM_FIELDS,
3096
+ VexAccessConfigError,
3097
+ VexAccessError,
3098
+ VexAuthConfigError,
3099
+ VexBlockValidationError,
3100
+ VexError,
3101
+ VexFieldValidationError,
3102
+ VexMediaConfigError,
3103
+ VexSlugConflictError,
3104
+ addRemovedFieldsAsOptional,
3105
+ array,
3106
+ blocks,
3107
+ checkbox,
3108
+ createDocument,
3109
+ date,
3110
+ defineAccess,
3111
+ defineBlock,
3112
+ defineCollection,
3113
+ defineConfig,
3114
+ defineMediaCollection,
3115
+ deleteDocument,
3116
+ deletePreviewSnapshot,
3117
+ diffSchema,
3118
+ extendTable,
3119
+ extractLivePreviewConfigs,
3120
+ extractUserFields,
3121
+ fieldMetaToZod,
3122
+ findCollectionBySlug,
3123
+ generateCollectionQueries,
3124
+ generateColumns,
3125
+ generateFormDefaultValues,
3126
+ generateFormSchema,
3127
+ generateIndexFile,
3128
+ generateVexSchema,
3129
+ generateVexTypes,
3130
+ getAllCollections,
3131
+ getDocument,
3132
+ getPreviewSnapshot,
3133
+ hasPermission,
3134
+ imageUrl,
3135
+ isMediaCollection,
3136
+ json,
3137
+ listDocuments,
3138
+ makeFieldsOptional,
3139
+ mergeAuthCollectionWithUserCollection,
3140
+ number,
3141
+ planMigration,
3142
+ relationship,
3143
+ resolvePreviewURL,
3144
+ richtext,
3145
+ sanitizeConfigForClient,
3146
+ searchDocuments,
3147
+ select,
3148
+ shouldReloadURL,
3149
+ slugToInterfaceName,
3150
+ text,
3151
+ toTitleCase,
3152
+ ui,
3153
+ updateDocument,
3154
+ upload,
3155
+ upsertPreviewSnapshot,
3156
+ vexQuery
3157
+ };
3158
+ //# sourceMappingURL=index.js.map