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
@@ -0,0 +1,101 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Generated utilities for implementing server-side Convex query and mutation functions.
4
+ *
5
+ * THIS CODE IS AUTOMATICALLY GENERATED.
6
+ *
7
+ * To regenerate, run `npx convex dev`.
8
+ * @module
9
+ */
10
+
11
+ import {
12
+ actionGeneric,
13
+ httpActionGeneric,
14
+ queryGeneric,
15
+ mutationGeneric,
16
+ internalActionGeneric,
17
+ internalMutationGeneric,
18
+ internalQueryGeneric,
19
+ } from "convex/server";
20
+
21
+ /**
22
+ * Define a query in this Convex app's public API.
23
+ *
24
+ * This function will be allowed to read your Convex database and will be accessible from the client.
25
+ *
26
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
27
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
28
+ */
29
+ export const query = queryGeneric;
30
+
31
+ /**
32
+ * Define a query that is only accessible from other Convex functions (but not from the client).
33
+ *
34
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
35
+ *
36
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
37
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
38
+ */
39
+ export const internalQuery = internalQueryGeneric;
40
+
41
+ /**
42
+ * Define a mutation in this Convex app's public API.
43
+ *
44
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
45
+ *
46
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
47
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
48
+ */
49
+ export const mutation = mutationGeneric;
50
+
51
+ /**
52
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
53
+ *
54
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
55
+ *
56
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
57
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
58
+ */
59
+ export const internalMutation = internalMutationGeneric;
60
+
61
+ /**
62
+ * Define an action in this Convex app's public API.
63
+ *
64
+ * An action is a function which can execute any JavaScript code, including non-deterministic
65
+ * code and code with side-effects, like calling third-party services.
66
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
67
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
68
+ *
69
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
70
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
71
+ */
72
+ export const action = actionGeneric;
73
+
74
+ /**
75
+ * Define an action that is only accessible from other Convex functions (but not from the client).
76
+ *
77
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
78
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
79
+ */
80
+ export const internalAction = internalActionGeneric;
81
+
82
+ /**
83
+ * Define an HTTP action.
84
+ *
85
+ * The wrapped function will be used to respond to HTTP requests received
86
+ * by a Convex deployment if the requests matches the path and method where
87
+ * this action is routed. Be sure to route your httpAction in `convex/http.js`.
88
+ *
89
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument
90
+ * and a Fetch API `Request` object as its second.
91
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
92
+ */
93
+ export const httpAction = httpActionGeneric;
94
+
95
+ /**
96
+ * Typesafe environment variables.
97
+ *
98
+ * This includes platform-provided env vars and any variables declared in
99
+ * `convex.config.ts`.
100
+ */
101
+ export const env = process.env;
@@ -1,14 +1,18 @@
1
+ import { TABLE_SLUG_FOOTERS } from "~/db/constants"
2
+ import { find } from "~/vexcms/api"
3
+
1
4
  import { query } from "./_generated/server"
2
5
 
3
- /**
4
- * Get the first published footer.
5
- * Used by the public site layout for server-side preloading.
6
- */
6
+ /** Same shape as `convex/headers.ts`'s `getFirst`, for `footers`. */
7
7
  export const getFirst = query({
8
8
  args: {},
9
9
  handler: async (ctx) => {
10
- const footer = await ctx.db.query("footers").first()
11
- if (!footer || footer.vex_status === "draft") return null
12
- return footer
10
+ const [footer] = await find({
11
+ ctx,
12
+ collection: TABLE_SLUG_FOOTERS,
13
+ limit: 1,
14
+ access: { bypass: true },
15
+ })
16
+ return footer ?? null
13
17
  },
14
18
  })
@@ -1,14 +1,24 @@
1
+ import { TABLE_SLUG_HEADERS } from "~/db/constants"
2
+ import { find } from "~/vexcms/api"
3
+
1
4
  import { query } from "./_generated/server"
2
5
 
3
6
  /**
4
- * Get the first published header.
5
- * Used by the public site layout for server-side preloading.
7
+ * The site header — `headers` has one document in practice (seeded as "Main
8
+ * Header"); this returns the first one found, or `null`.
9
+ *
10
+ * Access is bypassed: the header renders on every public route before any
11
+ * session exists.
6
12
  */
7
13
  export const getFirst = query({
8
14
  args: {},
9
15
  handler: async (ctx) => {
10
- const header = await ctx.db.query("headers").first()
11
- if (!header || header.vex_status === "draft") return null
12
- return header
16
+ const [header] = await find({
17
+ ctx,
18
+ collection: TABLE_SLUG_HEADERS,
19
+ limit: 1,
20
+ access: { bypass: true },
21
+ })
22
+ return header ?? null
13
23
  },
14
24
  })
@@ -1,46 +1,31 @@
1
- import { getPreviewSnapshot } from "@vexcms/core"
2
1
  import { v } from "convex/values"
3
2
 
4
3
  import { TABLE_SLUG_PAGES } from "~/db/constants"
4
+ import { find } from "~/vexcms/api"
5
5
 
6
- import { vexQuery } from "./vex/helpers"
6
+ import { query } from "./_generated/server"
7
7
 
8
8
  /**
9
- * Get a published page by slug.
10
- * Returns null if the page doesn't exist or isn't published.
9
+ * Returns the page document matching the given slug, or an empty array if
10
+ * none exists. Byte-identical in shape to `apps/test/convex/pages.ts:57-77`'s
11
+ * `getBySlug` (same collection-level `find` + `withIndex` + `access.bypass`
12
+ * pattern) with only the collection constant swapped.
11
13
  *
12
- * Uses vexQuery which automatically handles _vexDrafts arg
13
- * and provides ctx.drafts for draft-aware queries.
14
+ * Access is bypassed: rendered by `[slug]/page.tsx` and `page.tsx` for
15
+ * anonymous visitors, who have no roles and would otherwise be filtered out.
14
16
  */
15
- export const getBySlug = vexQuery({
17
+ export const getBySlug = query({
16
18
  args: { slug: v.string() },
17
- handler: async (ctx, args) => {
18
- const page = await ctx.db
19
- .query(TABLE_SLUG_PAGES)
20
- .withIndex("by_slug", (q) => q.eq("slug", args.slug))
21
- .first()
22
-
23
- if (!page) {
24
- return null
25
- }
26
-
27
- // On the public route, only return published pages
28
- if (ctx.drafts === false && page.vex_status !== "published") {
29
- return null
30
- }
31
-
32
- // For preview/snapshot mode, merge the draft snapshot
33
- if (ctx.drafts === "snapshot") {
34
- const snapshot = await getPreviewSnapshot({
35
- collection: TABLE_SLUG_PAGES,
36
- ctx,
37
- documentId: page._id,
38
- })
39
- if (snapshot) {
40
- return { ...page, ...snapshot }
41
- }
42
- }
43
-
44
- return page
19
+ handler: async (ctx, { slug }) => {
20
+ return await find({
21
+ ctx,
22
+ collection: TABLE_SLUG_PAGES,
23
+ withIndex: {
24
+ name: "by_slug",
25
+ range: (q) => q.eq("slug", slug),
26
+ },
27
+ limit: 1,
28
+ access: { bypass: true },
29
+ })
45
30
  },
46
31
  })
@@ -0,0 +1,119 @@
1
+ import { defineSchema, defineTable } from "convex/server"
2
+ import { v } from "convex/values"
3
+
4
+ import { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_USERS } from "~/db/constants"
5
+
6
+ import {
7
+ apikey,
8
+ images,
9
+ jwks,
10
+ session,
11
+ verification,
12
+ pages,
13
+ headers,
14
+ footers,
15
+ themes,
16
+ } from "./vex.schema"
17
+
18
+ export default defineSchema({
19
+ pages,
20
+ headers,
21
+ footers,
22
+ themes,
23
+ // `organization`/`team`/`teamMember`/`member`/`invitation`/`vex_globals` are
24
+ // hand-written, not imported from `./vex.schema` — the generator only emits
25
+ // them when the `organization()` plugin (`--orgs`) or a registered global is
26
+ // actually present in `vex.config.ts`. `access.ts` and every `getAuth`
27
+ // caller (`convex/vex.ts`, `convex/vex/media.ts`, `convex/vex/globals.ts`)
28
+ // reference `orgCollectionSlug`/`resolveOrgs: true` unconditionally, so
29
+ // these tables must exist in the schema from day one — enabling `--orgs`
30
+ // or registering a global later needs no schema migration.
31
+ vex_globals: defineTable({
32
+ slug: v.string(),
33
+ data: v.any(),
34
+ }).index("by_slug", ["slug"]),
35
+ images,
36
+ team: defineTable({
37
+ name: v.string(),
38
+ organizationId: v.string(),
39
+ createdAt: v.number(),
40
+ updatedAt: v.optional(v.number()),
41
+ }).index("by_organizationId", ["organizationId"]),
42
+ teamMember: defineTable({
43
+ teamId: v.string(),
44
+ userId: v.string(),
45
+ createdAt: v.optional(v.number()),
46
+ })
47
+ .index("by_teamId", ["teamId"])
48
+ .index("by_userId", ["userId"]),
49
+ organization: defineTable({
50
+ name: v.string(),
51
+ slug: v.string(),
52
+ logo: v.optional(v.string()),
53
+ createdAt: v.number(),
54
+ metadata: v.optional(v.string()),
55
+ }).index("by_slug", ["slug"]),
56
+ member: defineTable({
57
+ organizationId: v.string(),
58
+ userId: v.string(),
59
+ role: v.string(),
60
+ createdAt: v.number(),
61
+ })
62
+ .index("by_organizationId", ["organizationId"])
63
+ .index("by_userId", ["userId"]),
64
+ invitation: defineTable({
65
+ organizationId: v.string(),
66
+ email: v.string(),
67
+ role: v.optional(v.string()),
68
+ teamId: v.optional(v.string()),
69
+ status: v.string(),
70
+ expiresAt: v.number(),
71
+ createdAt: v.number(),
72
+ inviterId: v.string(),
73
+ roles: v.array(v.string()),
74
+ })
75
+ .index("by_organizationId", ["organizationId"])
76
+ .index("by_email", ["email"]),
77
+ session,
78
+ verification,
79
+ apikey,
80
+ jwks,
81
+
82
+ [TABLE_SLUG_USERS]: defineTable({
83
+ name: v.string(),
84
+ banExpires: v.optional(v.number()), // admin plugin
85
+ banned: v.optional(v.boolean()), // admin plugin
86
+ banReason: v.optional(v.string()), // admin plugin
87
+ createdAt: v.number(),
88
+ displayUsername: v.optional(v.union(v.null(), v.string())),
89
+ email: v.string(),
90
+ emailVerified: v.boolean(),
91
+ image: v.optional(v.string()),
92
+ isAnonymous: v.optional(v.union(v.null(), v.boolean())),
93
+ phoneNumber: v.optional(v.union(v.null(), v.string())),
94
+ phoneNumberVerified: v.optional(v.union(v.null(), v.boolean())),
95
+ role: v.optional(v.string()), // admin plugin — single string in BA 1.6
96
+ roles: v.array(v.string()), // our multi-role field via additionalFields
97
+ twoFactorEnabled: v.optional(v.union(v.null(), v.boolean())),
98
+ updatedAt: v.number(),
99
+ userId: v.optional(v.union(v.null(), v.string())),
100
+ username: v.optional(v.union(v.null(), v.string())),
101
+ }).index("by_email", ["email"]),
102
+
103
+ [TABLE_SLUG_ACCOUNTS]: defineTable({
104
+ accessToken: v.optional(v.string()),
105
+ accessTokenExpiresAt: v.optional(v.number()),
106
+ accountId: v.string(),
107
+ createdAt: v.number(),
108
+ idToken: v.optional(v.string()),
109
+ password: v.optional(v.string()),
110
+ providerId: v.string(),
111
+ refreshToken: v.optional(v.string()),
112
+ refreshTokenExpiresAt: v.optional(v.number()),
113
+ scope: v.optional(v.string()),
114
+ updatedAt: v.number(),
115
+ userId: v.string(),
116
+ })
117
+ .index("by_userId", ["userId"])
118
+ .index("by_accountId", ["accountId"]),
119
+ })