@vexcms/core 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @vexcms/core
2
+
3
+ The foundational package for [VEX CMS](https://github.com/vexcms) — a headless content management system built for [Convex](https://convex.dev).
4
+
5
+ `@vexcms/core` provides the configuration API, field type system, schema generation, type generation, and all core utilities that power the VEX CMS ecosystem. It has no direct Convex dependency — Convex is a peer dependency only.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @vexcms/core
11
+ ```
12
+
13
+ ## Features
14
+
15
+ ### Configuration API
16
+
17
+ Define your CMS structure with a type-safe, declarative API:
18
+
19
+ ```typescript
20
+ import { defineConfig, defineCollection, text, richtext, select } from "@vexcms/core"
21
+
22
+ const posts = defineCollection({
23
+ slug: "posts",
24
+ labels: { singular: "Post", plural: "Posts" },
25
+ fields: {
26
+ title: text({ label: "Title", required: true }),
27
+ content: richtext({ label: "Content" }),
28
+ status: select({
29
+ label: "Status",
30
+ options: [
31
+ { label: "Draft", value: "draft" },
32
+ { label: "Published", value: "published" },
33
+ ],
34
+ defaultValue: "draft",
35
+ }),
36
+ },
37
+ })
38
+
39
+ export default defineConfig({
40
+ collections: [posts],
41
+ admin: { user: "user" },
42
+ basePath: "/admin",
43
+ })
44
+ ```
45
+
46
+ ### Field Types
47
+
48
+ 13 built-in field types with full TypeScript inference:
49
+
50
+ | Field | Description |
51
+ |-------|-------------|
52
+ | `text` | String with optional min/max length |
53
+ | `number` | Numeric with optional min/max/step |
54
+ | `checkbox` | Boolean toggle |
55
+ | `select` | Single or multi-value enum with options |
56
+ | `date` | Date stored as epoch milliseconds |
57
+ | `imageUrl` | URL string for images |
58
+ | `relationship` | Reference to another collection (single or hasMany) |
59
+ | `upload` | Reference to media collection documents (single or hasMany) |
60
+ | `json` | Arbitrary JSON data |
61
+ | `array` | Wraps any field type in an array |
62
+ | `richtext` | Plate/Slate JSON editor documents |
63
+ | `ui` | Non-persisted custom render components |
64
+ | `blocks` | Ordered array of block instances (discriminated union) |
65
+
66
+ ### Blocks System
67
+
68
+ Define reusable content blocks for flexible page building:
69
+
70
+ ```typescript
71
+ import { defineBlock, text, richtext } from "@vexcms/core"
72
+
73
+ const heroBlock = defineBlock({
74
+ slug: "hero",
75
+ label: "Hero Section",
76
+ fields: {
77
+ heading: text({ label: "Heading", required: true }),
78
+ body: richtext({ label: "Body" }),
79
+ },
80
+ })
81
+ ```
82
+
83
+ ### Collections, Globals & Media
84
+
85
+ - **Collections** — Content types with typed fields, versioning/draft workflow, database indexes, search indexes, and admin UI configuration
86
+ - **Globals** — Singleton settings (site config, navigation, etc.) with the same field system
87
+ - **Media Collections** — File storage with auto-injected fields (storageId, filename, mimeType, size, url, alt, width, height)
88
+
89
+ ### Schema & Type Generation
90
+
91
+ Generates Convex schema and TypeScript types from your config:
92
+
93
+ ```typescript
94
+ import { generateVexSchema, generateVexTypes } from "@vexcms/core"
95
+
96
+ const schemaSource = generateVexSchema(config) // → vex.schema.ts
97
+ const typesSource = generateVexTypes(config) // → vex.types.ts
98
+ ```
99
+
100
+ ### Versioning & Drafts
101
+
102
+ Per-collection draft/publish workflow with autosave:
103
+
104
+ ```typescript
105
+ defineCollection({
106
+ slug: "posts",
107
+ versions: {
108
+ drafts: true,
109
+ autosave: { interval: 2000 },
110
+ maxPerDoc: 100,
111
+ },
112
+ // ...
113
+ })
114
+ ```
115
+
116
+ ### Access Control (RBAC)
117
+
118
+ Field-level and collection-level permissions:
119
+
120
+ ```typescript
121
+ import { defineAccess } from "@vexcms/core"
122
+
123
+ const access = defineAccess({
124
+ posts: {
125
+ read: true,
126
+ update: { mode: "allow", fields: ["title", "content"] },
127
+ delete: false,
128
+ },
129
+ })
130
+ ```
131
+
132
+ ### Auto-Migration
133
+
134
+ Schema diffing and migration planning for safe schema changes:
135
+
136
+ ```typescript
137
+ import { diffSchema, planMigration } from "@vexcms/core"
138
+
139
+ const diff = diffSchema(oldSchema, newSchema)
140
+ const ops = planMigration(config, oldSchema, newSchema)
141
+ ```
142
+
143
+ ### Live Preview
144
+
145
+ Per-collection iframe preview with responsive breakpoints and snapshot system.
146
+
147
+ ### Convex Integration Utilities
148
+
149
+ Generic document CRUD operations, query helpers with draft support, and preview snapshot management — all framework-agnostic.
150
+
151
+ ## Peer Dependencies
152
+
153
+ - `convex` — Convex backend
154
+ - `react` — React 18+
155
+ - `@tanstack/react-table` — Table utilities for admin column generation
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ComponentType } from 'react';
2
2
  import * as convex_server from 'convex/server';
3
- import { TableDefinition, GenericDataModel, GenericMutationCtx, GenericQueryCtx, TableNamesInDataModel, PaginationOptions } from 'convex/server';
4
- import { VObject, GenericValidator, ObjectType, PropertyValidators } from 'convex/values';
3
+ import { TableDefinition, GenericDataModel, GenericMutationCtx, GenericQueryCtx, QueryBuilder, RegisteredQuery, TableNamesInDataModel, PaginationOptions } from 'convex/server';
4
+ import { VObject, GenericValidator, ObjectType, PropertyValidators, v } from 'convex/values';
5
5
  import { ColumnDef } from '@tanstack/react-table';
6
6
  import { ZodTypeAny, z } from 'zod';
7
7
 
@@ -893,6 +893,12 @@ interface VexCollection<TFields extends Record<string, any> = any, TExtraKeys ex
893
893
  * Auth adapter collections default to `false` — set explicitly to opt in.
894
894
  */
895
895
  generateApi?: boolean;
896
+ /**
897
+ * Internal marker set by resolveMediaCollection().
898
+ * When true, ExtractFieldKeys includes DefaultMediaFieldKeys.
899
+ * @internal — do not set manually.
900
+ */
901
+ _isMedia?: true;
896
902
  /**
897
903
  * TypeScript interface name used in generated `vex.types.ts`.
898
904
  * If not set, auto-generated from slug via PascalCase conversion.
@@ -1094,6 +1100,11 @@ interface AdminConfig {
1094
1100
  };
1095
1101
  /** Global live preview defaults */
1096
1102
  livePreview?: AdminLivePreviewConfig;
1103
+ /** Onboarding tour configuration */
1104
+ onboarding: {
1105
+ /** Whether the onboarding tour is disabled. Default: false (tour enabled) */
1106
+ disabled: boolean;
1107
+ };
1097
1108
  }
1098
1109
  /**
1099
1110
  * Admin page metadata configuration.
@@ -1178,6 +1189,22 @@ interface AdminConfigInput {
1178
1189
  * Individual collections can override these breakpoints.
1179
1190
  */
1180
1191
  livePreview?: AdminLivePreviewConfig;
1192
+ /**
1193
+ * Onboarding tour configuration.
1194
+ *
1195
+ * Default:
1196
+ * ```
1197
+ * disabled: false
1198
+ * ```
1199
+ */
1200
+ onboarding?: {
1201
+ /**
1202
+ * Disable the onboarding tour for all users.
1203
+ *
1204
+ * Default: `false`
1205
+ */
1206
+ disabled?: boolean;
1207
+ };
1181
1208
  }
1182
1209
 
1183
1210
  /** Schema generation configuration. */
@@ -1362,7 +1389,9 @@ type ExtractSlug<T> = T extends {
1362
1389
  * @example
1363
1390
  * type K = ExtractFieldKeys<typeof posts>; // "title" | "slug" | "status" | "featured"
1364
1391
  */
1365
- type ExtractFieldKeys<T> = T extends VexCollection<infer TFields, infer TExtraKeys> ? (keyof TFields & string) | (TExtraKeys & string) : T extends VexMediaCollection<infer TFields> ? (keyof TFields & string) | DefaultMediaFieldKeys : T extends VexGlobal<infer TFields> ? keyof TFields & string : never;
1392
+ type ExtractFieldKeys<T> = T extends VexCollection<infer TFields, infer TExtraKeys> ? T extends {
1393
+ _isMedia: true;
1394
+ } ? (keyof TFields & string) | (TExtraKeys & string) | DefaultMediaFieldKeys : (keyof TFields & string) | (TExtraKeys & string) : T extends VexMediaCollection<infer TFields> ? (keyof TFields & string) | DefaultMediaFieldKeys : T extends VexGlobal<infer TFields> ? keyof TFields & string : never;
1366
1395
  /**
1367
1396
  * Extract the inferred document type from a VexCollection or VexGlobal.
1368
1397
  * Includes `_id` (system field) and auth extra keys (typed as `string`).
@@ -2286,12 +2315,17 @@ declare function deletePreviewSnapshot<DataModel extends GenericDataModel>(props
2286
2315
  documentId: string;
2287
2316
  }): Promise<void>;
2288
2317
  /**
2289
- * Gets the preview snapshot for a document, if one exists.
2318
+ * Gets the preview data for a document.
2319
+ *
2320
+ * Lookup order:
2321
+ * 1. Preview snapshot (transient, written by admin form on each change)
2322
+ * 2. Latest version from vex_versions (draft or published, excluding autosave/previewSnapshot)
2323
+ * 3. null (fall back to main document)
2290
2324
  *
2291
2325
  * @param props.ctx - Convex query context
2292
2326
  * @param props.collection - Collection slug
2293
2327
  * @param props.documentId - Document ID
2294
- * @returns The snapshot data, or null if no preview snapshot exists
2328
+ * @returns The snapshot data, or null if no preview/version exists
2295
2329
  */
2296
2330
  declare function getPreviewSnapshot<DataModel extends GenericDataModel>(props: {
2297
2331
  ctx: GenericQueryCtx<DataModel>;
@@ -2322,51 +2356,61 @@ interface VexQueryCtx<DataModel extends GenericDataModel = GenericDataModel> ext
2322
2356
  drafts: VexDraftsMode;
2323
2357
  }
2324
2358
  /**
2325
- * Creates a Convex query with built-in draft/snapshot content support.
2359
+ * Create a typed vexQuery builder from your project's query builder.
2326
2360
  *
2327
- * Wraps Convex's `queryGeneric()` to:
2328
- * 1. Automatically add an optional `_vexDrafts` arg (string | boolean)
2329
- * 2. Pass an extended context with `drafts` mode to the handler
2330
- * 3. Preserve full type safety for args and return types
2331
- *
2332
- * The handler receives a `VexQueryCtx` which includes `ctx.drafts`.
2333
- * Use this to decide what content to return.
2361
+ * Call this once in your project to get a `vexQuery` function that
2362
+ * preserves full return type inference from your DataModel.
2334
2363
  *
2335
2364
  * @example
2336
2365
  * ```ts
2337
- * import { vexQuery, getPreviewSnapshot } from "@vexcms/core";
2338
- * import { v } from "convex/values";
2366
+ * // convex/vex/helpers.ts
2367
+ * import { createVexQuery } from "@vexcms/core";
2368
+ * import { query } from "../_generated/server";
2369
+ *
2370
+ * export const vexQuery = createVexQuery(query);
2371
+ * ```
2339
2372
  *
2340
- * export const getPost = vexQuery({
2373
+ * Then use it in your query files:
2374
+ * ```ts
2375
+ * // convex/pages.ts
2376
+ * import { vexQuery } from "./vex/helpers";
2377
+ * import { getPreviewSnapshot } from "@vexcms/core";
2378
+ *
2379
+ * export const getBySlug = vexQuery({
2341
2380
  * args: { slug: v.string() },
2342
2381
  * handler: async (ctx, args) => {
2343
- * const post = await ctx.db
2344
- * .query("posts")
2382
+ * const page = await ctx.db
2383
+ * .query("pages")
2345
2384
  * .withIndex("by_slug", (q) => q.eq("slug", args.slug))
2346
2385
  * .first();
2347
- *
2348
- * if (!post) return null;
2349
- *
2386
+ * if (!page) return null;
2350
2387
  * if (ctx.drafts === "snapshot") {
2351
- * const snapshot = await getPreviewSnapshot({
2352
- * ctx,
2353
- * collection: "posts",
2354
- * documentId: post._id,
2355
- * });
2356
- * if (snapshot) {
2357
- * return { ...post, ...snapshot };
2358
- * }
2388
+ * const snapshot = await getPreviewSnapshot({ ctx, collection: "pages", documentId: page._id });
2389
+ * if (snapshot) return { ...page, ...snapshot };
2359
2390
  * }
2360
- *
2361
- * return post;
2391
+ * return page;
2362
2392
  * },
2363
2393
  * });
2364
2394
  * ```
2365
2395
  */
2396
+ declare function createVexQuery<DataModel extends GenericDataModel>(_queryBuilder: QueryBuilder<DataModel, "public">): <Args extends PropertyValidators, Output>(props: {
2397
+ args: Args;
2398
+ handler: (ctx: VexQueryCtx<DataModel>, args: ObjectType<Args>) => Output | Promise<Output>;
2399
+ }) => RegisteredQuery<"public", ObjectType<Args & {
2400
+ _vexDrafts: typeof v.optional<any>;
2401
+ }>, Awaited<Output>>;
2402
+ /**
2403
+ * Generic vexQuery for use without project-specific types.
2404
+ * Prefer `createVexQuery(query)` for full type inference.
2405
+ *
2406
+ * @deprecated Use `createVexQuery(query)` instead for proper return type inference.
2407
+ */
2366
2408
  declare function vexQuery<Args extends PropertyValidators, Output>(props: {
2367
2409
  args: Args;
2368
2410
  handler: (ctx: VexQueryCtx, args: ObjectType<Args>) => Output | Promise<Output>;
2369
- }): convex_server.RegisteredQuery<"public", any, Promise<Output>>;
2411
+ }): RegisteredQuery<"public", ObjectType<Args & {
2412
+ _vexDrafts: typeof v.optional<any>;
2413
+ }>, Awaited<Output>>;
2370
2414
 
2371
2415
  declare function listDocuments<DataModel extends GenericDataModel>(props: {
2372
2416
  args: {
@@ -2623,4 +2667,4 @@ declare function planMigration(props: {
2623
2667
  config: VexConfig;
2624
2668
  }): MigrationOp[];
2625
2669
 
2626
- export { ALL_SYSTEM_FIELDS, type AccessAction, type AdminConfig, type AdminConfigInput, type AdminLivePreviewConfig, type AdminMetaInput, type AdminSidebarInput, type AnyVexCollection, type ArrayFieldDef, type AuthCollectionFieldKeys, type AuthTableFieldKeys, type BlockAdminConfig, type BlockDef, type BlocksFieldDef, type CellComponentProps, type CheckboxFieldDef, type ClientMediaConfig, type ClientVexConfig, type CollectionAdminConfig, type CollectionKind, type CollectionQueryImports, DEFAULT_AUTOSAVE_INTERVAL, DEFAULT_BREAKPOINTS, DEFAULT_MAX_VERSIONS_PER_DOC, type DateFieldDef, type DefaultMediaFieldKeys, type DistributiveOmit, type ExtractDocType, type ExtractFieldKeys, type ExtractSlug, type ExtractSlugs, type FieldAdminConfig, type FieldComponentProps, type FieldPermissionResult, type FileStorageAdapter, GENERATED_HEADER, type GeneratedFiles, type GlobalAdminConfig, type ImageUrlFieldDef, type IndexConfig, type InferBlockUnion, type InferFieldType, type InferFieldsType, type JsonFieldDef, LOCKED_MEDIA_FIELDS, type LivePreviewBreakpoint, type LivePreviewConfig, type LockedMediaField, type LookupBySlug, type MediaConfig, type MediaConfigInput, type MergedCollectionResult, type MigrationOp, type NumberFieldDef, OVERRIDABLE_MEDIA_FIELDS, type OverridableMediaField, PREVIEW_SNAPSHOT_DEBOUNCE_MS, type PermissionCallbackProps, type PermissionCheck, type RelationshipFieldDef, type RemovedFieldInfo, type ResolvedCollectionMatch, type ResolvedFieldPermissions, type ResolvedIndex, type ResolvedSearchIndex, type ResourcePermissions, type RichTextDocument, type RichTextElement, type RichTextFieldDef, type RichTextText, type RolesWithPermissions, type SchemaDiff, type SchemaFieldInfo, type SearchIndexConfig, type SelectFieldDef, type SelectOption, type TextFieldDef, type UIFieldDef, type UploadFieldDef, VERSION_SYSTEM_FIELDS, type VersioningFieldKeys, type VersionsConfig, type VexAccessConfig, VexAccessConfigError, VexAccessError, type VexAccessInput, type VexAccessInputBase, type VexAccessInputWithOrg, type VexAuthAdapter, VexAuthConfigError, VexBlockValidationError, type VexCollection, type VexConfig, type VexConfigInput, type VexDraftsMode, type VexEditorAdapter, type VexEditorComponentProps, VexError, type VexField, VexFieldValidationError, type VexGlobal, type VexMediaCollection, VexMediaConfigError, type VexQueryCtx, type VexRenderComponentProps, VexSlugConflictError, addRemovedFieldsAsOptional, array, blocks, checkbox, createDocument, date, defineAccess, defineBlock, defineCollection, defineConfig, defineMediaCollection, deleteDocument, deletePreviewSnapshot, diffSchema, extendTable, extractLivePreviewConfigs, extractUserFields, fieldMetaToZod, findCollectionBySlug, generateCollectionQueries, generateColumns, generateFormDefaultValues, generateFormSchema, generateIndexFile, generateVexSchema, generateVexTypes, getAllCollections, getDocument, getPreviewSnapshot, hasPermission, imageUrl, isMediaCollection, json, listDocuments, makeFieldsOptional, mergeAuthCollectionWithUserCollection, number, planMigration, relationship, resolvePreviewURL, richtext, sanitizeConfigForClient, searchDocuments, select, shouldReloadURL, slugToInterfaceName, text, toTitleCase, ui, updateDocument, upload, upsertPreviewSnapshot, vexQuery };
2670
+ export { ALL_SYSTEM_FIELDS, type AccessAction, type AdminConfig, type AdminConfigInput, type AdminLivePreviewConfig, type AdminMetaInput, type AdminSidebarInput, type AnyVexCollection, type ArrayFieldDef, type AuthCollectionFieldKeys, type AuthTableFieldKeys, type BlockAdminConfig, type BlockDef, type BlocksFieldDef, type CellComponentProps, type CheckboxFieldDef, type ClientMediaConfig, type ClientVexConfig, type CollectionAdminConfig, type CollectionKind, type CollectionQueryImports, DEFAULT_AUTOSAVE_INTERVAL, DEFAULT_BREAKPOINTS, DEFAULT_MAX_VERSIONS_PER_DOC, type DateFieldDef, type DefaultMediaFieldKeys, type DistributiveOmit, type ExtractDocType, type ExtractFieldKeys, type ExtractSlug, type ExtractSlugs, type FieldAdminConfig, type FieldComponentProps, type FieldPermissionResult, type FileStorageAdapter, GENERATED_HEADER, type GeneratedFiles, type GlobalAdminConfig, type ImageUrlFieldDef, type IndexConfig, type InferBlockUnion, type InferFieldType, type InferFieldsType, type JsonFieldDef, LOCKED_MEDIA_FIELDS, type LivePreviewBreakpoint, type LivePreviewConfig, type LockedMediaField, type LookupBySlug, type MediaConfig, type MediaConfigInput, type MergedCollectionResult, type MigrationOp, type NumberFieldDef, OVERRIDABLE_MEDIA_FIELDS, type OverridableMediaField, PREVIEW_SNAPSHOT_DEBOUNCE_MS, type PermissionCallbackProps, type PermissionCheck, type RelationshipFieldDef, type RemovedFieldInfo, type ResolvedCollectionMatch, type ResolvedFieldPermissions, type ResolvedIndex, type ResolvedSearchIndex, type ResourcePermissions, type RichTextDocument, type RichTextElement, type RichTextFieldDef, type RichTextText, type RolesWithPermissions, type SchemaDiff, type SchemaFieldInfo, type SearchIndexConfig, type SelectFieldDef, type SelectOption, type TextFieldDef, type UIFieldDef, type UploadFieldDef, VERSION_SYSTEM_FIELDS, type VersioningFieldKeys, type VersionsConfig, type VexAccessConfig, VexAccessConfigError, VexAccessError, type VexAccessInput, type VexAccessInputBase, type VexAccessInputWithOrg, type VexAuthAdapter, VexAuthConfigError, VexBlockValidationError, type VexCollection, type VexConfig, type VexConfigInput, type VexDraftsMode, type VexEditorAdapter, type VexEditorComponentProps, VexError, type VexField, VexFieldValidationError, type VexGlobal, type VexMediaCollection, VexMediaConfigError, type VexQueryCtx, type VexRenderComponentProps, VexSlugConflictError, addRemovedFieldsAsOptional, array, blocks, checkbox, createDocument, createVexQuery, date, defineAccess, defineBlock, defineCollection, defineConfig, defineMediaCollection, deleteDocument, deletePreviewSnapshot, diffSchema, extendTable, extractLivePreviewConfigs, extractUserFields, fieldMetaToZod, findCollectionBySlug, generateCollectionQueries, generateColumns, generateFormDefaultValues, generateFormSchema, generateIndexFile, generateVexSchema, generateVexTypes, getAllCollections, getDocument, getPreviewSnapshot, hasPermission, imageUrl, isMediaCollection, json, listDocuments, makeFieldsOptional, mergeAuthCollectionWithUserCollection, number, planMigration, relationship, resolvePreviewURL, richtext, sanitizeConfigForClient, searchDocuments, select, shouldReloadURL, slugToInterfaceName, text, toTitleCase, ui, updateDocument, upload, upsertPreviewSnapshot, vexQuery };
package/dist/index.js CHANGED
@@ -137,6 +137,9 @@ var BASE_VEX_CONFIG = {
137
137
  user: "users",
138
138
  sidebar: {
139
139
  hideGlobals: false
140
+ },
141
+ onboarding: {
142
+ disabled: false
140
143
  }
141
144
  },
142
145
  schema: {
@@ -170,7 +173,8 @@ function resolveMediaCollection(props) {
170
173
  fields: defaults,
171
174
  tableName: props.mediaCollection.tableName,
172
175
  labels: props.mediaCollection.labels,
173
- admin: adminConfig
176
+ admin: adminConfig,
177
+ _isMedia: true
174
178
  };
175
179
  }
176
180
  function defineConfig(vexConfig) {
@@ -189,6 +193,10 @@ function defineConfig(vexConfig) {
189
193
  ...BASE_VEX_CONFIG.admin.sidebar,
190
194
  ...vexConfig.admin?.sidebar
191
195
  },
196
+ onboarding: {
197
+ ...BASE_VEX_CONFIG.admin.onboarding,
198
+ ...vexConfig.admin?.onboarding
199
+ },
192
200
  livePreview: vexConfig.admin?.livePreview
193
201
  },
194
202
  schema: {
@@ -1956,7 +1964,7 @@ function fieldToTypeString(props) {
1956
1964
  case "json":
1957
1965
  return "Record<string, unknown>";
1958
1966
  case "richtext":
1959
- return "any";
1967
+ return "RichTextDocument";
1960
1968
  case "ui":
1961
1969
  return "never";
1962
1970
  case "select": {
@@ -2105,10 +2113,37 @@ function generateVexTypes(props) {
2105
2113
  if (config.collections.length > 0 || (config.media?.collections?.length ?? 0) > 0 || config.globals.length > 0) {
2106
2114
  needsIdImport = true;
2107
2115
  }
2116
+ let needsRichTextImport = false;
2117
+ function checkForRichTextFields(fields) {
2118
+ for (const field of Object.values(fields)) {
2119
+ if (field.type === "richtext") {
2120
+ needsRichTextImport = true;
2121
+ }
2122
+ }
2123
+ }
2124
+ for (const col of config.collections) {
2125
+ checkForRichTextFields(col.fields);
2126
+ }
2127
+ if (config.media?.collections) {
2128
+ for (const col of config.media.collections) {
2129
+ checkForRichTextFields(col.fields);
2130
+ }
2131
+ }
2132
+ for (const g of config.globals) {
2133
+ checkForRichTextFields(g.fields);
2134
+ }
2135
+ for (const block of blocksBySlug.values()) {
2136
+ checkForRichTextFields(block.fields);
2137
+ }
2108
2138
  parts.push("// \u26A0\uFE0F AUTO-GENERATED BY VEX CMS \u2014 DO NOT EDIT \u26A0\uFE0F");
2109
2139
  parts.push("");
2110
2140
  if (needsIdImport) {
2111
2141
  parts.push("import type { Id } from './_generated/dataModel';");
2142
+ }
2143
+ if (needsRichTextImport) {
2144
+ parts.push("import type { RichTextDocument } from '@vexcms/core';");
2145
+ }
2146
+ if (needsIdImport || needsRichTextImport) {
2112
2147
  parts.push("");
2113
2148
  }
2114
2149
  const sortedBlockSlugs = [...blocksBySlug.keys()].sort();
@@ -2399,12 +2434,31 @@ async function deletePreviewSnapshot(props) {
2399
2434
  }
2400
2435
  }
2401
2436
  async function getPreviewSnapshot(props) {
2402
- const entry = await props.ctx.db.query("vex_versions").withIndex(
2437
+ const previewEntry = await props.ctx.db.query("vex_versions").withIndex(
2403
2438
  "by_document_status",
2404
2439
  (q) => q.eq("collection", props.collection).eq("documentId", props.documentId).eq("status", "previewSnapshot")
2405
2440
  ).first();
2406
- if (!entry) return null;
2407
- return entry.snapshot;
2441
+ if (previewEntry) {
2442
+ return previewEntry.snapshot;
2443
+ }
2444
+ const allVersions = await props.ctx.db.query("vex_versions").withIndex(
2445
+ "by_document",
2446
+ (q) => q.eq("collection", props.collection).eq("documentId", props.documentId)
2447
+ ).collect();
2448
+ let latestVersion = null;
2449
+ let maxVersion = -1;
2450
+ for (const v2 of allVersions) {
2451
+ if (v2.status === "previewSnapshot" || v2.status === "autosave") continue;
2452
+ const ver = v2.version;
2453
+ if (ver > maxVersion) {
2454
+ maxVersion = ver;
2455
+ latestVersion = v2;
2456
+ }
2457
+ }
2458
+ if (latestVersion) {
2459
+ return latestVersion.snapshot;
2460
+ }
2461
+ return null;
2408
2462
  }
2409
2463
 
2410
2464
  // src/convex/vexQuery.ts
@@ -2412,6 +2466,30 @@ import {
2412
2466
  queryGeneric
2413
2467
  } from "convex/server";
2414
2468
  import { v } from "convex/values";
2469
+ function wrapHandler(handler) {
2470
+ return async (ctx, args) => {
2471
+ const { _vexDrafts, ...userArgs } = args;
2472
+ const drafts = _vexDrafts !== void 0 ? _vexDrafts : "snapshot";
2473
+ const vexCtx = Object.assign(
2474
+ Object.create(Object.getPrototypeOf(ctx)),
2475
+ ctx,
2476
+ { drafts }
2477
+ );
2478
+ return handler(vexCtx, userArgs);
2479
+ };
2480
+ }
2481
+ function createVexQuery(_queryBuilder) {
2482
+ return (props) => {
2483
+ const mergedArgs = {
2484
+ ...props.args,
2485
+ _vexDrafts: v.optional(v.union(v.literal("snapshot"), v.boolean()))
2486
+ };
2487
+ return queryGeneric({
2488
+ args: mergedArgs,
2489
+ handler: wrapHandler(props.handler)
2490
+ });
2491
+ };
2492
+ }
2415
2493
  function vexQuery(props) {
2416
2494
  const mergedArgs = {
2417
2495
  ...props.args,
@@ -2419,14 +2497,7 @@ function vexQuery(props) {
2419
2497
  };
2420
2498
  return queryGeneric({
2421
2499
  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
- }
2500
+ handler: wrapHandler(props.handler)
2430
2501
  });
2431
2502
  }
2432
2503
 
@@ -3106,6 +3177,7 @@ export {
3106
3177
  blocks,
3107
3178
  checkbox,
3108
3179
  createDocument,
3180
+ createVexQuery,
3109
3181
  date,
3110
3182
  defineAccess,
3111
3183
  defineBlock,