create-vexcms 0.0.19 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +93 -83
  3. package/dist/index.js +246 -81
  4. package/dist/index.mjs +30976 -0
  5. package/package.json +35 -15
  6. package/templates/base-nextjs/README.md +53 -205
  7. package/templates/base-nextjs/_env.example +7 -9
  8. package/templates/base-nextjs/_gitignore +41 -1
  9. package/templates/base-nextjs/convex/_generated/api.d.ts +6 -62
  10. package/templates/base-nextjs/convex/_generated/server.d.ts +19 -0
  11. package/templates/base-nextjs/convex/_generated/server.js +8 -0
  12. package/templates/base-nextjs/convex/auth/api.ts +49 -3
  13. package/templates/base-nextjs/convex/auth/db.ts +42 -300
  14. package/templates/base-nextjs/convex/auth/index.ts +11 -7
  15. package/templates/base-nextjs/convex/auth/options.ts +5 -29
  16. package/templates/base-nextjs/convex/auth/plugins/index.ts +15 -5
  17. package/templates/base-nextjs/convex/auth/sessions.ts +9 -15
  18. package/templates/base-nextjs/convex/http.ts +18 -3
  19. package/templates/base-nextjs/convex/schema.ts +104 -2
  20. package/templates/base-nextjs/convex/vex/firstUser.ts +10 -84
  21. package/templates/base-nextjs/convex/vex/globals.ts +19 -82
  22. package/templates/base-nextjs/convex/vex/media.ts +19 -246
  23. package/templates/base-nextjs/convex/vex.schema.ts +138 -0
  24. package/templates/base-nextjs/convex/vex.ts +19 -0
  25. package/templates/base-nextjs/convex.json +6 -0
  26. package/templates/base-nextjs/eslint.config.mjs +1 -21
  27. package/templates/base-nextjs/next.config.ts +10 -0
  28. package/templates/base-nextjs/package.json +47 -48
  29. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +17 -5
  30. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +2 -4
  31. package/templates/base-nextjs/src/app/(frontend)/layout.tsx +12 -2
  32. package/templates/base-nextjs/src/app/(frontend)/page.tsx +2 -113
  33. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +40 -0
  34. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +22 -0
  35. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +19 -0
  36. package/templates/base-nextjs/src/app/globals.css +240 -104
  37. package/templates/base-nextjs/src/app/layout.tsx +23 -13
  38. package/templates/base-nextjs/src/app/unauthorized/page.tsx +24 -0
  39. package/templates/base-nextjs/src/auth/access.ts +46 -0
  40. package/templates/base-nextjs/src/auth/client.tsx +17 -5
  41. package/templates/base-nextjs/src/auth/hasPermission.ts +39 -0
  42. package/templates/base-nextjs/src/auth/serverUtils.ts +15 -22
  43. package/templates/base-nextjs/src/components/WelcomePage.tsx +116 -0
  44. package/templates/base-nextjs/src/components/component-example.tsx +56 -464
  45. package/templates/base-nextjs/src/components/providers/auth.tsx +13 -0
  46. package/templates/base-nextjs/src/components/providers/client.tsx +6 -1
  47. package/templates/base-nextjs/src/components/providers/convex.tsx +53 -21
  48. package/templates/base-nextjs/src/components/providers/server.tsx +10 -7
  49. package/templates/{marketing-site → base-nextjs}/src/components/ui/accordion.tsx +30 -32
  50. package/templates/base-nextjs/src/components/ui/badge.tsx +10 -13
  51. package/templates/base-nextjs/src/components/ui/input.tsx +35 -5
  52. package/templates/base-nextjs/src/context/AuthContext.tsx +29 -0
  53. package/templates/base-nextjs/src/db/constants/auth.ts +0 -1
  54. package/templates/base-nextjs/src/db/constants/index.ts +22 -35
  55. package/templates/base-nextjs/src/lib/utils.ts +0 -12
  56. package/templates/base-nextjs/src/proxy.ts +98 -11
  57. package/templates/base-nextjs/src/vex.config.ts +35 -0
  58. package/templates/base-nextjs/src/vex.types.ts +293 -0
  59. package/templates/base-nextjs/src/vexcms/collections/images.ts +15 -0
  60. package/templates/base-nextjs/src/vexcms/collections/index.ts +2 -1
  61. package/templates/base-nextjs/src/vexcms/collections/users.ts +7 -35
  62. package/templates/base-nextjs/tsconfig.json +3 -0
  63. package/templates/marketing-site/convex/_generated/api.d.ts +81 -0
  64. package/templates/marketing-site/convex/_generated/api.js +23 -0
  65. package/templates/marketing-site/convex/_generated/dataModel.d.ts +60 -0
  66. package/templates/marketing-site/convex/_generated/server.d.ts +162 -0
  67. package/templates/marketing-site/convex/_generated/server.js +101 -0
  68. package/templates/marketing-site/convex/footers.ts +11 -7
  69. package/templates/marketing-site/convex/headers.ts +15 -5
  70. package/templates/marketing-site/convex/pages.ts +20 -35
  71. package/templates/marketing-site/convex/schema.ts +119 -0
  72. package/templates/marketing-site/convex/seed.ts +582 -35
  73. package/templates/marketing-site/convex/siteSettings.ts +19 -5
  74. package/templates/marketing-site/convex/theme.ts +55 -61
  75. package/templates/marketing-site/convex/vex.schema.ts +355 -0
  76. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +27 -160
  77. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +6 -9
  78. package/templates/marketing-site/src/app/(frontend)/layout.tsx +4 -2
  79. package/templates/marketing-site/src/app/(frontend)/page.tsx +2 -5
  80. package/templates/marketing-site/src/components/SiteFooter.tsx +6 -3
  81. package/templates/marketing-site/src/components/SiteHeader.tsx +6 -3
  82. package/templates/marketing-site/src/components/ThemeLive.tsx +59 -0
  83. package/templates/marketing-site/src/components/ThemeStyle.tsx +45 -89
  84. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +2 -2
  85. package/templates/marketing-site/src/lib/metadata.ts +51 -72
  86. package/templates/marketing-site/src/vex.config.ts +35 -0
  87. package/templates/marketing-site/src/vex.types.ts +634 -0
  88. package/templates/marketing-site/src/vexcms/api.ts +23 -0
  89. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -3
  90. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +2 -2
  91. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -4
  92. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +2 -2
  93. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +4 -8
  94. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +2 -2
  95. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +7 -11
  96. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +1 -1
  97. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +9 -9
  98. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +4 -5
  99. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +3 -4
  100. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -19
  101. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +4 -9
  102. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +3 -6
  103. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +28 -68
  104. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +13 -43
  105. package/templates/marketing-site/src/vexcms/blocks/config.ts +7 -22
  106. package/templates/marketing-site/src/vexcms/blocks/constants.ts +2 -3
  107. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -8
  108. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -1
  109. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -1
  110. package/templates/marketing-site/src/vexcms/collections/index.ts +2 -1
  111. package/templates/marketing-site/src/vexcms/collections/pages.ts +12 -21
  112. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +118 -0
  113. package/templates/marketing-site/src/vexcms/collections/themes.ts +29 -111
  114. package/templates/marketing-site/src/vexcms/globals/index.ts +1 -1
  115. package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +43 -27
  116. package/templates/base-nextjs/convex/auth/adapter/index.ts +0 -230
  117. package/templates/base-nextjs/convex/auth/adapter/utils.ts +0 -547
  118. package/templates/base-nextjs/convex/auth/config.ts +0 -10
  119. package/templates/base-nextjs/convex/vex/auth.ts +0 -38
  120. package/templates/base-nextjs/convex/vex/collections.ts +0 -321
  121. package/templates/base-nextjs/convex/vex/helpers.ts +0 -33
  122. package/templates/base-nextjs/convex/vex/impersonation.ts +0 -51
  123. package/templates/base-nextjs/convex/vex/migrate.ts +0 -74
  124. package/templates/base-nextjs/convex/vex/model/collections.ts +0 -196
  125. package/templates/base-nextjs/convex/vex/model/media.ts +0 -87
  126. package/templates/base-nextjs/convex/vex/model/versions.ts +0 -254
  127. package/templates/base-nextjs/convex/vex/previewSnapshot.ts +0 -53
  128. package/templates/base-nextjs/convex/vex/versions.ts +0 -648
  129. package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +0 -33
  130. package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +0 -38
  131. package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +0 -101
  132. package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +0 -15
  133. package/templates/base-nextjs/src/app/admin/layout.tsx +0 -51
  134. package/templates/base-nextjs/src/app/favicon.ico +0 -0
  135. package/templates/base-nextjs/src/components/providers/theme.tsx +0 -11
  136. package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +0 -67
  137. package/templates/base-nextjs/src/db/types.ts +0 -6
  138. package/templates/base-nextjs/src/vexcms/access.ts +0 -44
  139. package/templates/base-nextjs/src/vexcms/auth.ts +0 -4
  140. package/templates/base-nextjs/src/vexcms/collections/media.ts +0 -15
  141. package/templates/base-nextjs/vex.config.ts +0 -25
  142. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  143. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +0 -55
  144. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +0 -27
  145. package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +0 -18
  146. package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +0 -71
  147. package/templates/marketing-site/src/components/ThemeInjector.tsx +0 -128
  148. package/templates/marketing-site/src/components/admin/IconPickerField.tsx +0 -134
  149. package/templates/marketing-site/src/components/admin/ThemeImport.tsx +0 -174
  150. package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +0 -33
  151. package/templates/marketing-site/src/db/constants/index.ts +0 -52
  152. package/templates/marketing-site/src/lib/colorConvert.ts +0 -171
  153. package/templates/marketing-site/src/vexcms/access.ts +0 -36
  154. package/templates/marketing-site/vex.config.ts +0 -32
@@ -1,14 +1,28 @@
1
+ import { getGlobal } from "@vexcms/core/server"
2
+
3
+ import { GLOBAL_SLUG_SITE_SETTINGS } from "~/db/constants"
4
+ import config from "~/vex.config"
5
+
1
6
  import { query } from "./_generated/server"
2
7
 
3
8
  /**
4
- * Get the published site settings.
5
- * Used by server components for SEO metadata generation.
9
+ * Public site settings — name, description, SEO defaults, and the theme
10
+ * references. `siteSettings` lives in the shared `vex_globals` table, so
11
+ * this reads through `getGlobal` rather than a dedicated table (unlike the
12
+ * reference's `ctx.db.query("site_settings")`, which assumes a table that
13
+ * doesn't exist under the current architecture).
14
+ *
15
+ * Access is bypassed: read by `src/lib/metadata.ts` and the root layout for
16
+ * anonymous visitors before any session exists.
6
17
  */
7
18
  export const get = query({
8
19
  args: {},
9
20
  handler: async (ctx) => {
10
- const settings = await ctx.db.query("site_settings").first()
11
- if (!settings) return null
12
- return settings
21
+ return await getGlobal({
22
+ ctx,
23
+ config,
24
+ slug: GLOBAL_SLUG_SITE_SETTINGS,
25
+ access: { bypass: true },
26
+ })
13
27
  },
14
28
  })
@@ -1,75 +1,69 @@
1
- import { getPreviewSnapshot } from "@vexcms/core"
1
+ import { getGlobal } from "@vexcms/core/server"
2
+
3
+ import { GLOBAL_SLUG_SITE_SETTINGS, type TABLE_SLUG_THEMES } from "~/db/constants"
4
+ import config from "~/vex.config"
5
+
6
+ import type { Doc, Id } from "./_generated/dataModel"
7
+ import type { QueryCtx } from "./_generated/server"
2
8
 
3
9
  import { query } from "./_generated/server"
4
- import * as Versions from "./vex/model/versions"
10
+
11
+ /** A theme document, or `null` when none is selected. */
12
+ type ActiveTheme = Doc<typeof TABLE_SLUG_THEMES> | null
5
13
 
6
14
  /**
7
- * Get the active theme from site settings (published).
8
- * Returns the full theme document or null if no active theme is set.
9
- * Used for server-side CSS variable injection on public pages.
15
+ * Resolves one of `siteSettings`' theme references to its document.
16
+ *
17
+ * `relationship` always stores an array of ids — `hasMany` only controls how
18
+ * many the admin picker lets you choose — so the first entry is the selection.
19
+ *
20
+ * Access control is bypassed: a site's palette is public by definition, and an
21
+ * anonymous visitor must get the same colours as a signed-in editor.
22
+ *
23
+ * @param props - Input props.
24
+ * @param props.ctx - Convex query context.
25
+ * @param props.field - Which `siteSettings` reference to follow.
26
+ * @returns The referenced theme, or `null` when the global is unset, the
27
+ * reference is empty, or the referenced theme has been deleted.
10
28
  */
11
- export const getActive = query({
12
- args: {},
13
- handler: async (ctx) => {
14
- const settings = await ctx.db.query("site_settings").first()
15
- if (!settings?.activeTheme) return null
29
+ async function resolveTheme(props: {
30
+ ctx: QueryCtx
31
+ field: "activeTheme" | "adminTheme"
32
+ }): Promise<ActiveTheme> {
33
+ const settings = await getGlobal({
34
+ ctx: props.ctx,
35
+ config,
36
+ slug: GLOBAL_SLUG_SITE_SETTINGS,
37
+ access: { bypass: true },
38
+ })
39
+ if (!settings) return null
16
40
 
17
- const theme = await ctx.db.get(settings.activeTheme)
18
- if (!theme) return null
41
+ const reference = settings[props.field] as string[] | undefined
42
+ const themeId = reference?.[0]
43
+ if (!themeId) return null
19
44
 
20
- // Only return published themes
21
- if (theme.vex_status === "draft") return null
45
+ return await props.ctx.db.get(themeId as Id<typeof TABLE_SLUG_THEMES>)
46
+ }
22
47
 
23
- return theme
24
- },
48
+ /**
49
+ * The theme applied to the public site — `siteSettings.activeTheme`.
50
+ *
51
+ * Read by `<ThemeStyle />` in the root layout on every render.
52
+ */
53
+ export const getActive = query({
54
+ args: {},
55
+ handler: async (ctx): Promise<ActiveTheme> => await resolveTheme({ ctx, field: "activeTheme" }),
25
56
  })
26
57
 
27
58
  /**
28
- * Get the active theme for preview/draft mode.
29
- * Reads site settings with preview snapshot overlay to get the draft activeTheme,
30
- * then fetches the latest version of that theme (draft or published).
31
- * Used for server-side CSS variable injection on preview pages.
59
+ * The theme applied to the admin panel.
60
+ *
61
+ * Falls back to `activeTheme` when `adminTheme` is unset, which is the default:
62
+ * the admin adopts the site's palette. Setting `adminTheme` is the opt-out.
32
63
  */
33
- export const getActivePreview = query({
64
+ export const getAdmin = query({
34
65
  args: {},
35
- handler: async (ctx) => {
36
- // Get site settings, overlaying any preview snapshot
37
- const settings = await ctx.db.query("site_settings").first()
38
- if (!settings) return null
39
-
40
- const settingsId = settings._id as string
41
-
42
- // Check for a preview snapshot on site_settings
43
- const snapshot = await getPreviewSnapshot({
44
- ctx,
45
- collection: "site_settings",
46
- documentId: settingsId,
47
- })
48
-
49
- const merged = snapshot
50
- ? { ...settings, ...(snapshot as Record<string, unknown>) }
51
- : settings
52
-
53
- const activeThemeId = (merged as any).activeTheme as string | undefined
54
- if (!activeThemeId) return null
55
-
56
- // Get the theme — prefer the latest draft version
57
- const theme = await ctx.db.get(activeThemeId as any)
58
- if (!theme) return null
59
-
60
- const latestVersion = await Versions.getLatestVersion({
61
- ctx,
62
- collection: "themes",
63
- documentId: activeThemeId,
64
- })
65
-
66
- if (latestVersion?.snapshot) {
67
- return {
68
- ...theme,
69
- ...(latestVersion.snapshot as Record<string, unknown>),
70
- }
71
- }
72
-
73
- return theme
74
- },
66
+ handler: async (ctx): Promise<ActiveTheme> =>
67
+ (await resolveTheme({ ctx, field: "adminTheme" })) ??
68
+ (await resolveTheme({ ctx, field: "activeTheme" })),
75
69
  })
@@ -0,0 +1,355 @@
1
+ // ⚠️ AUTO-GENERATED BY VEX CMS — DO NOT EDIT ⚠️
2
+ // Run 'vex dev' or 'vex generate' to update this file.
3
+
4
+ import { defineTable } from "convex/server"
5
+ import { v } from "convex/values"
6
+
7
+ export const user = defineTable({
8
+ name: v.string(),
9
+ email: v.string(),
10
+ emailVerified: v.boolean(),
11
+ image: v.optional(v.string()),
12
+ createdAt: v.number(),
13
+ updatedAt: v.number(),
14
+ role: v.optional(v.string()),
15
+ banned: v.optional(v.boolean()),
16
+ banReason: v.optional(v.string()),
17
+ banExpires: v.optional(v.number()),
18
+ isAnonymous: v.optional(v.boolean()),
19
+ userId: v.optional(v.string()),
20
+ roles: v.array(v.string()),
21
+ }).index("by_email", ["email"])
22
+
23
+ export const pages = defineTable({
24
+ title: v.string(),
25
+ slug: v.string(),
26
+ blocks: v.optional(
27
+ v.array(
28
+ v.union(
29
+ v.object({
30
+ blockType: v.literal("hero"),
31
+ blockName: v.optional(v.string()),
32
+ id: v.string(),
33
+ badgeText: v.optional(v.string()),
34
+ badgeLink: v.optional(v.string()),
35
+ heading: v.string(),
36
+ subheading: v.optional(v.string()),
37
+ primaryCtaLabel: v.string(),
38
+ primaryCtaHref: v.string(),
39
+ secondaryCtaLabel: v.optional(v.string()),
40
+ secondaryCtaHref: v.optional(v.string()),
41
+ }),
42
+ v.object({
43
+ blockType: v.literal("features"),
44
+ blockName: v.optional(v.string()),
45
+ id: v.string(),
46
+ heading: v.string(),
47
+ subheading: v.optional(v.string()),
48
+ features: v.array(
49
+ v.object({ title: v.string(), description: v.string(), icon: v.optional(v.string()) })
50
+ ),
51
+ }),
52
+ v.object({
53
+ blockType: v.literal("how_it_works"),
54
+ blockName: v.optional(v.string()),
55
+ id: v.string(),
56
+ heading: v.string(),
57
+ subheading: v.optional(v.string()),
58
+ steps: v.array(
59
+ v.object({ icon: v.optional(v.string()), title: v.string(), description: v.string() })
60
+ ),
61
+ }),
62
+ v.object({
63
+ blockType: v.literal("roadmap"),
64
+ blockName: v.optional(v.string()),
65
+ id: v.string(),
66
+ heading: v.string(),
67
+ subheading: v.optional(v.string()),
68
+ items: v.array(
69
+ v.object({
70
+ feature: v.string(),
71
+ description: v.optional(v.string()),
72
+ status: v.array(
73
+ v.union(v.literal("shipped"), v.literal("coming-soon"), v.literal("planned"))
74
+ ),
75
+ })
76
+ ),
77
+ }),
78
+ v.object({
79
+ blockType: v.literal("cta"),
80
+ blockName: v.optional(v.string()),
81
+ id: v.string(),
82
+ heading: v.string(),
83
+ subheading: v.optional(v.string()),
84
+ actions: v.optional(v.array(v.object({ label: v.string(), href: v.string() }))),
85
+ }),
86
+ v.object({
87
+ blockType: v.literal("faq"),
88
+ blockName: v.optional(v.string()),
89
+ id: v.string(),
90
+ heading: v.string(),
91
+ subheading: v.optional(v.string()),
92
+ supportLink: v.optional(v.string()),
93
+ items: v.array(v.object({ question: v.string(), answer: v.string() })),
94
+ })
95
+ )
96
+ )
97
+ ),
98
+ metaTitle: v.optional(v.string()),
99
+ metaDescription: v.optional(v.string()),
100
+ ogImage: v.optional(v.array(v.id("images"))),
101
+ }).index("by_slug", ["slug"])
102
+
103
+ export const headers = defineTable({
104
+ name: v.string(),
105
+ content: v.optional(
106
+ v.array(
107
+ v.object({
108
+ blockType: v.literal("header"),
109
+ blockName: v.optional(v.string()),
110
+ id: v.string(),
111
+ logoText: v.optional(v.string()),
112
+ logoImage: v.optional(v.array(v.id("images"))),
113
+ logoHref: v.optional(v.string()),
114
+ menuItems: v.optional(v.array(v.object({ label: v.string(), href: v.string() }))),
115
+ actionButtons: v.optional(
116
+ v.array(
117
+ v.object({
118
+ label: v.string(),
119
+ href: v.string(),
120
+ variant: v.optional(
121
+ v.array(v.union(v.literal("default"), v.literal("outline"), v.literal("ghost")))
122
+ ),
123
+ })
124
+ )
125
+ ),
126
+ })
127
+ )
128
+ ),
129
+ }).index("by_name", ["name"])
130
+
131
+ export const footers = defineTable({
132
+ name: v.string(),
133
+ content: v.optional(
134
+ v.array(
135
+ v.object({
136
+ blockType: v.literal("footer"),
137
+ blockName: v.optional(v.string()),
138
+ id: v.string(),
139
+ logoText: v.optional(v.string()),
140
+ logoImage: v.optional(v.array(v.id("images"))),
141
+ copyright: v.optional(v.string()),
142
+ links: v.optional(v.array(v.object({ label: v.string(), href: v.string() }))),
143
+ socialLinks: v.optional(
144
+ v.array(
145
+ v.object({ platform: v.string(), href: v.string(), icon: v.optional(v.string()) })
146
+ )
147
+ ),
148
+ })
149
+ )
150
+ ),
151
+ }).index("by_name", ["name"])
152
+
153
+ export const themes = defineTable({
154
+ name: v.string(),
155
+ fontFamily: v.optional(v.string()),
156
+ radius: v.optional(v.string()),
157
+ light: v.optional(
158
+ v.object({
159
+ background: v.optional(v.string()),
160
+ foreground: v.optional(v.string()),
161
+ card: v.optional(v.string()),
162
+ cardForeground: v.optional(v.string()),
163
+ popover: v.optional(v.string()),
164
+ popoverForeground: v.optional(v.string()),
165
+ primary: v.optional(v.string()),
166
+ primaryForeground: v.optional(v.string()),
167
+ secondary: v.optional(v.string()),
168
+ secondaryForeground: v.optional(v.string()),
169
+ muted: v.optional(v.string()),
170
+ mutedForeground: v.optional(v.string()),
171
+ accent: v.optional(v.string()),
172
+ accentForeground: v.optional(v.string()),
173
+ destructive: v.optional(v.string()),
174
+ destructiveForeground: v.optional(v.string()),
175
+ border: v.optional(v.string()),
176
+ input: v.optional(v.string()),
177
+ ring: v.optional(v.string()),
178
+ chart1: v.optional(v.string()),
179
+ chart2: v.optional(v.string()),
180
+ chart3: v.optional(v.string()),
181
+ chart4: v.optional(v.string()),
182
+ chart5: v.optional(v.string()),
183
+ sidebar: v.optional(v.string()),
184
+ sidebarForeground: v.optional(v.string()),
185
+ sidebarPrimary: v.optional(v.string()),
186
+ sidebarPrimaryForeground: v.optional(v.string()),
187
+ sidebarAccent: v.optional(v.string()),
188
+ sidebarAccentForeground: v.optional(v.string()),
189
+ sidebarBorder: v.optional(v.string()),
190
+ sidebarRing: v.optional(v.string()),
191
+ })
192
+ ),
193
+ dark: v.optional(
194
+ v.object({
195
+ background: v.optional(v.string()),
196
+ foreground: v.optional(v.string()),
197
+ card: v.optional(v.string()),
198
+ cardForeground: v.optional(v.string()),
199
+ popover: v.optional(v.string()),
200
+ popoverForeground: v.optional(v.string()),
201
+ primary: v.optional(v.string()),
202
+ primaryForeground: v.optional(v.string()),
203
+ secondary: v.optional(v.string()),
204
+ secondaryForeground: v.optional(v.string()),
205
+ muted: v.optional(v.string()),
206
+ mutedForeground: v.optional(v.string()),
207
+ accent: v.optional(v.string()),
208
+ accentForeground: v.optional(v.string()),
209
+ destructive: v.optional(v.string()),
210
+ destructiveForeground: v.optional(v.string()),
211
+ border: v.optional(v.string()),
212
+ input: v.optional(v.string()),
213
+ ring: v.optional(v.string()),
214
+ chart1: v.optional(v.string()),
215
+ chart2: v.optional(v.string()),
216
+ chart3: v.optional(v.string()),
217
+ chart4: v.optional(v.string()),
218
+ chart5: v.optional(v.string()),
219
+ sidebar: v.optional(v.string()),
220
+ sidebarForeground: v.optional(v.string()),
221
+ sidebarPrimary: v.optional(v.string()),
222
+ sidebarPrimaryForeground: v.optional(v.string()),
223
+ sidebarAccent: v.optional(v.string()),
224
+ sidebarAccentForeground: v.optional(v.string()),
225
+ sidebarBorder: v.optional(v.string()),
226
+ sidebarRing: v.optional(v.string()),
227
+ })
228
+ ),
229
+ }).index("by_name", ["name"])
230
+
231
+ export const session = defineTable({
232
+ expiresAt: v.number(),
233
+ token: v.string(),
234
+ createdAt: v.number(),
235
+ updatedAt: v.number(),
236
+ ipAddress: v.optional(v.string()),
237
+ userAgent: v.optional(v.string()),
238
+ userId: v.string(),
239
+ impersonatedBy: v.optional(v.string()),
240
+ activeOrganizationId: v.optional(v.string()),
241
+ })
242
+ .index("by_token", ["token"])
243
+ .index("by_userId", ["userId"])
244
+
245
+ export const account = defineTable({
246
+ accountId: v.string(),
247
+ providerId: v.string(),
248
+ userId: v.string(),
249
+ accessToken: v.optional(v.string()),
250
+ refreshToken: v.optional(v.string()),
251
+ idToken: v.optional(v.string()),
252
+ accessTokenExpiresAt: v.optional(v.number()),
253
+ refreshTokenExpiresAt: v.optional(v.number()),
254
+ scope: v.optional(v.string()),
255
+ password: v.optional(v.string()),
256
+ createdAt: v.number(),
257
+ updatedAt: v.number(),
258
+ }).index("by_userId", ["userId"])
259
+
260
+ export const verification = defineTable({
261
+ identifier: v.string(),
262
+ value: v.string(),
263
+ expiresAt: v.number(),
264
+ createdAt: v.number(),
265
+ updatedAt: v.number(),
266
+ }).index("by_identifier", ["identifier"])
267
+
268
+ export const apikey = defineTable({
269
+ configId: v.string(),
270
+ name: v.optional(v.string()),
271
+ start: v.optional(v.string()),
272
+ referenceId: v.string(),
273
+ prefix: v.optional(v.string()),
274
+ key: v.string(),
275
+ refillInterval: v.optional(v.number()),
276
+ refillAmount: v.optional(v.number()),
277
+ lastRefillAt: v.optional(v.number()),
278
+ enabled: v.optional(v.boolean()),
279
+ rateLimitEnabled: v.optional(v.boolean()),
280
+ rateLimitTimeWindow: v.optional(v.number()),
281
+ rateLimitMax: v.optional(v.number()),
282
+ requestCount: v.optional(v.number()),
283
+ remaining: v.optional(v.number()),
284
+ lastRequest: v.optional(v.number()),
285
+ expiresAt: v.optional(v.number()),
286
+ createdAt: v.number(),
287
+ updatedAt: v.number(),
288
+ permissions: v.optional(v.string()),
289
+ metadata: v.optional(v.string()),
290
+ })
291
+ .index("by_configId", ["configId"])
292
+ .index("by_referenceId", ["referenceId"])
293
+ .index("by_key", ["key"])
294
+
295
+ export const jwks = defineTable({
296
+ publicKey: v.string(),
297
+ privateKey: v.string(),
298
+ createdAt: v.number(),
299
+ expiresAt: v.optional(v.number()),
300
+ })
301
+
302
+ export const organization = defineTable({
303
+ name: v.string(),
304
+ slug: v.string(),
305
+ logo: v.optional(v.string()),
306
+ createdAt: v.number(),
307
+ metadata: v.optional(v.string()),
308
+ }).index("by_slug", ["slug"])
309
+
310
+ export const member = defineTable({
311
+ organizationId: v.string(),
312
+ userId: v.string(),
313
+ role: v.string(),
314
+ createdAt: v.number(),
315
+ })
316
+ .index("by_organizationId", ["organizationId"])
317
+ .index("by_userId", ["userId"])
318
+
319
+ export const invitation = defineTable({
320
+ organizationId: v.string(),
321
+ email: v.string(),
322
+ role: v.optional(v.string()),
323
+ status: v.string(),
324
+ expiresAt: v.number(),
325
+ createdAt: v.number(),
326
+ inviterId: v.string(),
327
+ })
328
+ .index("by_organizationId", ["organizationId"])
329
+ .index("by_email", ["email"])
330
+
331
+ export const images = defineTable({
332
+ filename: v.string(),
333
+ alt: v.string(),
334
+ mimeType: v.string(),
335
+ size: v.number(),
336
+ storageId: v.string(),
337
+ deleted: v.optional(v.boolean()),
338
+ src: v.string(),
339
+ width: v.optional(v.number()),
340
+ height: v.optional(v.number()),
341
+ })
342
+ .index("by_deleted", ["deleted"])
343
+ .searchIndex("search_filename", {
344
+ searchField: "filename",
345
+ filterFields: ["alt"],
346
+ })
347
+
348
+ /**
349
+ * VEX GLOBALS — singleton documents, one row per registered global slug
350
+ **/
351
+
352
+ export const vex_globals = defineTable({
353
+ slug: v.string(),
354
+ data: v.any(),
355
+ }).index("by_slug", ["slug"])