@vexcms/better-auth 0.1.0-alpha.22 → 0.1.0-alpha.23

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.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAKpB,cAAc,EACf,MAAM,cAAc,CAAC;AAWtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGvE;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AA+BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAmGlF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,QAOA"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAKpB,cAAc,EACf,MAAM,cAAc,CAAC;AAWtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIvE;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AA+BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAmGlF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,QAOA"}
@@ -193,4 +193,4 @@ function betterAuthAttrToVexField(fieldName, attr, tableSlug, authOptions) {
193
193
  export {
194
194
  betterAuthAdapter
195
195
  };
196
- //# sourceMappingURL=chunk-YPIEILO5.js.map
196
+ //# sourceMappingURL=chunk-KNQKC62W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n AuthCollectionConfig,\n AuthFieldMeta,\n BaseFieldInput,\n CollectionSlug,\n FieldAdminConfigInput,\n VexAuthAdapter,\n} from \"@vexcms/core\";\nimport {\n array,\n AuthCollectionMeta,\n checkbox,\n date,\n defineCollection,\n number,\n select,\n text,\n} from \"@vexcms/core\";\nimport type { BetterAuthOptions, DBFieldAttribute } from \"better-auth\";\nimport { getAuthTables } from \"better-auth/db\";\nimport type { VexResourceSlug } from \"@vexcms/core\";\n\n/**\n * Options for `betterAuthAdapter()`.\n *\n * Accepts the same `BetterAuthOptions` object you pass to `betterAuth()`\n * on the server. Only schema-affecting properties (modelNames,\n * additionalFields, plugins) are read. Runtime options (database, secret,\n * baseURL) are ignored.\n */\nexport interface BetterAuthAdapterOptions {\n config: BetterAuthOptions;\n}\n\nconst AUTH_COLLECTION_TYPES = {\n user: \"user\",\n session: \"session\",\n account: \"account\",\n verification: \"verification\",\n} as const;\n\n/** Fields on the user table that end-users are allowed to edit. */\nconst EDITABLE_FIELDS = new Set([\n \"name\",\n \"email\",\n \"image\",\n \"role\",\n \"banned\",\n \"banReason\",\n \"banExpires\",\n]);\n\n/** Fields that should be hidden in the admin UI for security. */\nconst HIDDEN_FIELDS = new Set([\n \"hashedPassword\",\n \"password\",\n \"twoFactorSecret\",\n \"twoFactorBackupCodes\",\n \"token\",\n \"secret\",\n \"code\",\n]);\n\n/**\n * Creates a Vex auth adapter from a Better Auth configuration.\n *\n * Introspects Better Auth's full merged schema (base fields + plugin fields\n * + additionalFields) via `getAuthTables()` and converts each table into a\n * standard Vex `CollectionConfig` using Vex field builders. Server-only: pass\n * the resulting `VexAuthAdapter` to `defineServerConfig({ server: { auth: { adapter } } })`,\n * which also uses its `collections` as the source of truth to verify that the client config's\n * `authCollections` — built by {@link better-auth/src!betterAuthCollections} from\n * `@vexcms/better-auth/client` — have not drifted, throwing `VexAuthConfigError` naming the\n * first divergence.\n *\n * Mappings from Better Auth `DBFieldAttribute` to Vex fields:\n * - `type` → Vex field type (`string`→text, `boolean`→checkbox, `number`→number,\n * `date`/`timestamp`→date, `json`→text fallback, array→select)\n * - `required` → field `required`\n * - `defaultValue` → field `defaultValue`\n * - `unique`/`index` → field `index` (index name: `by_<field>_unique` or `by_<field>`)\n * - `references` → `relationship` field\n * - System fields get `admin.readOnly: true` and `meta.locked: true`\n * - Sensitive fields get `admin.hidden: true`\n *\n * @param props — Adapter options; pass your Better Auth config object wrapped\n * in `{ config: authOptions }`. Optional — when omitted, uses default\n * Better Auth tables with no plugins or additional fields.\n * @returns A `VexAuthAdapter` ready for `defineServerConfig({ server: { auth: { adapter } } })`.\n *\n * @example\n * ```ts\n * // vex.config.server.ts\n * import { betterAuthAdapter } from \"@vexcms/better-auth\";\n * import { defineServerConfig } from \"@vexcms/core\";\n *\n * import { authOptions } from \"~/auth/server\";\n *\n * import vexConfig from \"./vex.config\";\n *\n * export default defineServerConfig({\n * config: vexConfig,\n * server: {\n * auth: { adapter: betterAuthAdapter({ config: authOptions }) },\n * },\n * });\n * ```\n */\nexport function betterAuthAdapter(props?: BetterAuthAdapterOptions): VexAuthAdapter {\n const tables = getAuthTables(props?.config ?? {});\n const collections: AuthCollectionConfig[] = [];\n const authCollections = [\n { type: AUTH_COLLECTION_TYPES.user, slug: props?.config?.user?.modelName },\n {\n type: AUTH_COLLECTION_TYPES.session,\n slug: props?.config?.session?.modelName,\n },\n {\n type: AUTH_COLLECTION_TYPES.account,\n slug: props?.config?.account?.modelName,\n },\n {\n type: AUTH_COLLECTION_TYPES.verification,\n slug: props?.config?.verification?.modelName,\n },\n ] as const;\n\n for (const [tableKey, tableDef] of Object.entries(tables)) {\n const slug = tableDef.modelName ?? tableKey;\n const fields: AuthCollectionConfig[\"fields\"] = {};\n\n addAuthCollectionFields({\n attributes: tableDef.fields,\n slug,\n fields,\n config: props?.config,\n extractId: true,\n });\n\n const authCollectionType = authCollections.find((ac) => ac.slug === slug)?.type;\n if (authCollectionType) {\n switch (authCollectionType) {\n case \"user\": {\n const additionalFields = props?.config?.user?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"session\": {\n const additionalFields = props?.config?.session?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"account\": {\n const additionalFields = props?.config?.account?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"verification\": {\n const additionalFields = props?.config?.verification?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n default:\n break;\n }\n }\n\n const isProtected = slug !== \"user\" && slug !== \"users\";\n\n collections.push(\n defineCollection<AuthFieldMeta, AuthCollectionMeta>({\n slug: slug as CollectionSlug,\n fields,\n admin: {\n icon: \"Lock\",\n },\n meta: isProtected ? { protected: true } : undefined,\n }),\n );\n }\n\n const userSlug = tables.user?.modelName ?? \"user\";\n return {\n name: \"better-auth\",\n collections,\n userCollection: userSlug as CollectionSlug,\n };\n}\n\n/**\n * Converts a set of Better Auth field attributes into Vex field definitions\n * and appends them to the given collection `fields` map.\n *\n * Iterates over `attributes`, calls {@link betterAuthAttrToVexField} for each\n * entry, and mutates `props.fields` in place. When `extractId` is `true`,\n * the `id` field is skipped because Convex auto-generates `_id`.\n *\n * @param props — Field extraction options.\n * @param props.attributes — Better Auth `DBFieldAttribute` map to convert.\n * @param props.slug — Collection slug (used for relationship target resolution).\n * @param props.fields — Mutable Vex collection fields object to populate.\n * @param props.config — Better Auth options (for model name / additional fields lookups).\n * @param props.extractId — When `true`, skips the `id` field.\n *\n * @internal\n */\nexport function addAuthCollectionFields(props: {\n attributes?: Record<string, DBFieldAttribute>;\n slug: string;\n fields: AuthCollectionConfig[\"fields\"];\n config?: BetterAuthOptions;\n extractId?: boolean;\n}) {\n if (!props.attributes) return;\n for (const [fieldName, attr] of Object.entries(props.attributes)) {\n if (props.extractId === true && fieldName === \"id\") continue;\n const field = betterAuthAttrToVexField(fieldName, attr, props.slug, props?.config);\n if (field) props.fields[fieldName] = field;\n }\n}\n\n/**\n * Maps a single Better Auth `DBFieldAttribute` to a Vex field builder call.\n *\n * Handles all Better Auth scalar types: `string`, `boolean`, `number`, `date`,\n * `timestamp`, `json`, and enum arrays. Maps overlapping properties\n * (`required`, `defaultValue`, `unique`/`index`, `references`) to their\n * Vex equivalents. Sets `admin.readOnly` and `meta.locked` on system\n * fields, and `admin.hidden` on sensitive fields.\n *\n * @param fieldName — The field name from Better Auth.\n * @param attr — The Better Auth field attribute.\n * @param tableSlug — The collection slug (for relationship targets).\n * @param authOptions -- The betterAuth options\n * @returns A Vex field instance, or `null` if the type is unsupported.\n *\n * @internal\n */\nfunction betterAuthAttrToVexField(\n fieldName: string,\n attr: DBFieldAttribute,\n tableSlug: string,\n authOptions?: BetterAuthOptions,\n) {\n // Skip id — Convex auto-generates _id\n if (fieldName === \"id\") return null;\n\n const isUserTable =\n tableSlug === authOptions?.user?.modelName || tableSlug === \"user\" || tableSlug === \"users\";\n const isEditable = isUserTable && EDITABLE_FIELDS.has(fieldName);\n const isHidden = HIDDEN_FIELDS.has(fieldName);\n\n const admin: FieldAdminConfigInput = {};\n if (!isEditable) admin.readOnly = true;\n if (isHidden) admin.hidden = true;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const baseOptions: { defaultValue: any } & BaseFieldInput<VexResourceSlug, AuthFieldMeta> = {\n defaultValue: undefined,\n };\n if (Object.keys(admin).length > 0) baseOptions.admin = admin;\n if (!isEditable) baseOptions.meta = { locked: true };\n if (attr.required) baseOptions.required = attr.required;\n if (attr.defaultValue !== undefined && typeof attr.defaultValue !== \"function\") {\n baseOptions.defaultValue = attr.defaultValue;\n }\n if (attr.unique || attr.index) {\n baseOptions.index = `by_${fieldName}`;\n }\n\n if (attr.references) {\n return text<VexResourceSlug, AuthFieldMeta>(baseOptions);\n }\n\n if (Array.isArray(attr.type)) {\n return select({\n options: attr.type.map((v: string) => ({ value: v, label: v })),\n ...baseOptions,\n });\n }\n\n switch (attr.type) {\n case \"string\":\n return text<VexResourceSlug, AuthFieldMeta>(baseOptions);\n case \"string[]\":\n return array({\n ...baseOptions,\n items: text<VexResourceSlug, AuthFieldMeta>(baseOptions),\n });\n case \"boolean\":\n return checkbox(baseOptions);\n case \"number\":\n return number<VexResourceSlug, AuthFieldMeta>(baseOptions);\n case \"number[]\":\n return array({\n ...baseOptions,\n items: number<VexResourceSlug, AuthFieldMeta>(baseOptions),\n });\n case \"date\":\n return date(baseOptions);\n case \"json\":\n // Fall back to text until a dedicated json field type is wired\n // TODO: create json field\n return text<VexResourceSlug, AuthFieldMeta>(baseOptions);\n default:\n // Unknown type — skip rather than crash\n return null;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  betterAuthAdapter
3
- } from "./chunk-YPIEILO5.js";
3
+ } from "./chunk-KNQKC62W.js";
4
4
 
5
5
  // src/pluginSchemas.generated.ts
6
6
  var PLUGIN_SCHEMAS = {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  betterAuthAdapter
3
- } from "./chunk-YPIEILO5.js";
3
+ } from "./chunk-KNQKC62W.js";
4
4
 
5
5
  // src/convex/adapter.ts
6
6
  import { anyApi } from "convex/server";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vexcms/better-auth",
3
- "version": "0.1.0-alpha.22",
3
+ "version": "0.1.0-alpha.23",
4
4
  "description": "better-auth adapter for VexCMS on Convex",
5
5
  "keywords": [
6
6
  "cms",
@@ -40,7 +40,7 @@
40
40
  "peerDependencies": {
41
41
  "better-auth": ">=1.6.23 <1.7.0",
42
42
  "convex": ">=1.44.0 <2",
43
- "@vexcms/core": "^0.1.0-alpha.22"
43
+ "@vexcms/core": "^0.1.0-alpha.23"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@better-auth/api-key": "1.6.23",
@@ -54,7 +54,7 @@
54
54
  "tsup": "8.5.1",
55
55
  "typescript": "6.0.3",
56
56
  "vitest": "4.1.10",
57
- "@vexcms/core": "0.1.0-alpha.22",
57
+ "@vexcms/core": "0.1.0-alpha.23",
58
58
  "@vexcms/tsconfig": "0.0.0"
59
59
  },
60
60
  "publishConfig": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n AuthCollectionConfig,\n AuthFieldMeta,\n BaseFieldInput,\n CollectionSlug,\n FieldAdminConfigInput,\n VexAuthAdapter,\n} from \"@vexcms/core\";\nimport {\n array,\n AuthCollectionMeta,\n checkbox,\n date,\n defineCollection,\n number,\n select,\n text,\n} from \"@vexcms/core\";\nimport type { BetterAuthOptions, DBFieldAttribute } from \"better-auth\";\nimport { getAuthTables } from \"better-auth/db\";\n\n/**\n * Options for `betterAuthAdapter()`.\n *\n * Accepts the same `BetterAuthOptions` object you pass to `betterAuth()`\n * on the server. Only schema-affecting properties (modelNames,\n * additionalFields, plugins) are read. Runtime options (database, secret,\n * baseURL) are ignored.\n */\nexport interface BetterAuthAdapterOptions {\n config: BetterAuthOptions;\n}\n\nconst AUTH_COLLECTION_TYPES = {\n user: \"user\",\n session: \"session\",\n account: \"account\",\n verification: \"verification\",\n} as const;\n\n/** Fields on the user table that end-users are allowed to edit. */\nconst EDITABLE_FIELDS = new Set([\n \"name\",\n \"email\",\n \"image\",\n \"role\",\n \"banned\",\n \"banReason\",\n \"banExpires\",\n]);\n\n/** Fields that should be hidden in the admin UI for security. */\nconst HIDDEN_FIELDS = new Set([\n \"hashedPassword\",\n \"password\",\n \"twoFactorSecret\",\n \"twoFactorBackupCodes\",\n \"token\",\n \"secret\",\n \"code\",\n]);\n\n/**\n * Creates a Vex auth adapter from a Better Auth configuration.\n *\n * Introspects Better Auth's full merged schema (base fields + plugin fields\n * + additionalFields) via `getAuthTables()` and converts each table into a\n * standard Vex `CollectionConfig` using Vex field builders. Server-only: pass\n * the resulting `VexAuthAdapter` to `defineServerConfig({ server: { auth: { adapter } } })`,\n * which also uses its `collections` as the source of truth to verify that the client config's\n * `authCollections` — built by {@link better-auth/src!betterAuthCollections} from\n * `@vexcms/better-auth/client` — have not drifted, throwing `VexAuthConfigError` naming the\n * first divergence.\n *\n * Mappings from Better Auth `DBFieldAttribute` to Vex fields:\n * - `type` → Vex field type (`string`→text, `boolean`→checkbox, `number`→number,\n * `date`/`timestamp`→date, `json`→text fallback, array→select)\n * - `required` → field `required`\n * - `defaultValue` → field `defaultValue`\n * - `unique`/`index` → field `index` (index name: `by_<field>_unique` or `by_<field>`)\n * - `references` → `relationship` field\n * - System fields get `admin.readOnly: true` and `meta.locked: true`\n * - Sensitive fields get `admin.hidden: true`\n *\n * @param props — Adapter options; pass your Better Auth config object wrapped\n * in `{ config: authOptions }`. Optional — when omitted, uses default\n * Better Auth tables with no plugins or additional fields.\n * @returns A `VexAuthAdapter` ready for `defineServerConfig({ server: { auth: { adapter } } })`.\n *\n * @example\n * ```ts\n * // vex.config.server.ts\n * import { betterAuthAdapter } from \"@vexcms/better-auth\";\n * import { defineServerConfig } from \"@vexcms/core\";\n *\n * import { authOptions } from \"~/auth/server\";\n *\n * import vexConfig from \"./vex.config\";\n *\n * export default defineServerConfig({\n * config: vexConfig,\n * server: {\n * auth: { adapter: betterAuthAdapter({ config: authOptions }) },\n * },\n * });\n * ```\n */\nexport function betterAuthAdapter(props?: BetterAuthAdapterOptions): VexAuthAdapter {\n const tables = getAuthTables(props?.config ?? {});\n const collections: AuthCollectionConfig[] = [];\n const authCollections = [\n { type: AUTH_COLLECTION_TYPES.user, slug: props?.config?.user?.modelName },\n {\n type: AUTH_COLLECTION_TYPES.session,\n slug: props?.config?.session?.modelName,\n },\n {\n type: AUTH_COLLECTION_TYPES.account,\n slug: props?.config?.account?.modelName,\n },\n {\n type: AUTH_COLLECTION_TYPES.verification,\n slug: props?.config?.verification?.modelName,\n },\n ] as const;\n\n for (const [tableKey, tableDef] of Object.entries(tables)) {\n const slug = tableDef.modelName ?? tableKey;\n const fields: AuthCollectionConfig[\"fields\"] = {};\n\n addAuthCollectionFields({\n attributes: tableDef.fields,\n slug,\n fields,\n config: props?.config,\n extractId: true,\n });\n\n const authCollectionType = authCollections.find((ac) => ac.slug === slug)?.type;\n if (authCollectionType) {\n switch (authCollectionType) {\n case \"user\": {\n const additionalFields = props?.config?.user?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"session\": {\n const additionalFields = props?.config?.session?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"account\": {\n const additionalFields = props?.config?.account?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n case \"verification\": {\n const additionalFields = props?.config?.verification?.additionalFields;\n addAuthCollectionFields({\n attributes: additionalFields,\n slug,\n fields,\n config: props?.config,\n });\n break;\n }\n default:\n break;\n }\n }\n\n const isProtected = slug !== \"user\" && slug !== \"users\";\n\n collections.push(\n defineCollection<AuthFieldMeta, AuthCollectionMeta>({\n slug: slug as CollectionSlug,\n fields,\n admin: {\n icon: \"Lock\",\n },\n meta: isProtected ? { protected: true } : undefined,\n }),\n );\n }\n\n const userSlug = tables.user?.modelName ?? \"user\";\n return {\n name: \"better-auth\",\n collections,\n userCollection: userSlug as CollectionSlug,\n };\n}\n\n/**\n * Converts a set of Better Auth field attributes into Vex field definitions\n * and appends them to the given collection `fields` map.\n *\n * Iterates over `attributes`, calls {@link betterAuthAttrToVexField} for each\n * entry, and mutates `props.fields` in place. When `extractId` is `true`,\n * the `id` field is skipped because Convex auto-generates `_id`.\n *\n * @param props — Field extraction options.\n * @param props.attributes — Better Auth `DBFieldAttribute` map to convert.\n * @param props.slug — Collection slug (used for relationship target resolution).\n * @param props.fields — Mutable Vex collection fields object to populate.\n * @param props.config — Better Auth options (for model name / additional fields lookups).\n * @param props.extractId — When `true`, skips the `id` field.\n *\n * @internal\n */\nexport function addAuthCollectionFields(props: {\n attributes?: Record<string, DBFieldAttribute>;\n slug: string;\n fields: AuthCollectionConfig[\"fields\"];\n config?: BetterAuthOptions;\n extractId?: boolean;\n}) {\n if (!props.attributes) return;\n for (const [fieldName, attr] of Object.entries(props.attributes)) {\n if (props.extractId === true && fieldName === \"id\") continue;\n const field = betterAuthAttrToVexField(fieldName, attr, props.slug, props?.config);\n if (field) props.fields[fieldName] = field;\n }\n}\n\n/**\n * Maps a single Better Auth `DBFieldAttribute` to a Vex field builder call.\n *\n * Handles all Better Auth scalar types: `string`, `boolean`, `number`, `date`,\n * `timestamp`, `json`, and enum arrays. Maps overlapping properties\n * (`required`, `defaultValue`, `unique`/`index`, `references`) to their\n * Vex equivalents. Sets `admin.readOnly` and `meta.locked` on system\n * fields, and `admin.hidden` on sensitive fields.\n *\n * @param fieldName — The field name from Better Auth.\n * @param attr — The Better Auth field attribute.\n * @param tableSlug — The collection slug (for relationship targets).\n * @param authOptions -- The betterAuth options\n * @returns A Vex field instance, or `null` if the type is unsupported.\n *\n * @internal\n */\nfunction betterAuthAttrToVexField(\n fieldName: string,\n attr: DBFieldAttribute,\n tableSlug: string,\n authOptions?: BetterAuthOptions,\n) {\n // Skip id — Convex auto-generates _id\n if (fieldName === \"id\") return null;\n\n const isUserTable =\n tableSlug === authOptions?.user?.modelName || tableSlug === \"user\" || tableSlug === \"users\";\n const isEditable = isUserTable && EDITABLE_FIELDS.has(fieldName);\n const isHidden = HIDDEN_FIELDS.has(fieldName);\n\n const admin: FieldAdminConfigInput = {};\n if (!isEditable) admin.readOnly = true;\n if (isHidden) admin.hidden = true;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const baseOptions: { defaultValue: any } & BaseFieldInput<AuthFieldMeta> = {\n defaultValue: undefined,\n };\n if (Object.keys(admin).length > 0) baseOptions.admin = admin;\n if (!isEditable) baseOptions.meta = { locked: true };\n if (attr.required) baseOptions.required = attr.required;\n if (attr.defaultValue !== undefined && typeof attr.defaultValue !== \"function\") {\n baseOptions.defaultValue = attr.defaultValue;\n }\n if (attr.unique || attr.index) {\n baseOptions.index = `by_${fieldName}`;\n }\n\n if (attr.references) {\n return text(baseOptions);\n }\n\n if (Array.isArray(attr.type)) {\n return select({\n options: attr.type.map((v: string) => ({ value: v, label: v })),\n ...baseOptions,\n });\n }\n\n switch (attr.type) {\n case \"string\":\n return text(baseOptions);\n case \"string[]\":\n return array({\n ...baseOptions,\n items: text(baseOptions),\n });\n case \"boolean\":\n return checkbox(baseOptions);\n case \"number\":\n return number(baseOptions);\n case \"number[]\":\n return array({\n ...baseOptions,\n items: number(baseOptions),\n });\n case \"date\":\n return date(baseOptions);\n case \"json\":\n // Fall back to text until a dedicated json field type is wired\n // TODO: create json field\n return text(baseOptions);\n default:\n // Unknown type — skip rather than crash\n return null;\n }\n}\n"],"mappings":"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","names":[]}