@onmax/nuxt-better-auth 0.0.2-alpha.14 → 0.0.2-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@onmax/nuxt-better-auth",
3
+ "version": "0.0.2-alpha.16",
3
4
  "configKey": "auth",
4
5
  "compatibility": {
5
6
  "nuxt": ">=3.0.0"
6
7
  },
7
- "version": "0.0.2-alpha.14",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,13 +1,18 @@
1
- import { existsSync } from 'node:fs';
1
+ import { randomBytes } from 'node:crypto';
2
+ import { existsSync, writeFileSync, readFileSync } from 'node:fs';
2
3
  import { mkdir, writeFile } from 'node:fs/promises';
3
- import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages } from '@nuxt/kit';
4
+ import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, installModule, extendPages } from '@nuxt/kit';
4
5
  import { consola as consola$1 } from 'consola';
5
6
  import { defu } from 'defu';
6
- import { join } from 'pathe';
7
+ import { join, dirname } from 'pathe';
7
8
  import { toRouteMatcher, createRouter } from 'radix3';
9
+ import { isCI, isTest } from 'std-env';
8
10
  import { generateDrizzleSchema as generateDrizzleSchema$1 } from '@better-auth/cli/api';
11
+ import { getAuthTables } from 'better-auth/db';
9
12
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
10
13
 
14
+ const version = "0.0.2-alpha.16";
15
+
11
16
  function setupDevTools(nuxt) {
12
17
  nuxt.hook("devtools:customTabs", (tabs) => {
13
18
  tabs.push({
@@ -52,6 +57,101 @@ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
52
57
  }
53
58
  return result.code;
54
59
  }
60
+ const convexIndexFields = {
61
+ account: ["accountId", ["accountId", "providerId"], ["providerId", "userId"]],
62
+ rateLimit: ["key"],
63
+ session: ["expiresAt", ["expiresAt", "userId"]],
64
+ verification: ["expiresAt", "identifier"],
65
+ user: [["email", "name"], "name", "userId"],
66
+ passkey: ["credentialID"],
67
+ oauthConsent: [["clientId", "userId"]]
68
+ };
69
+ function getConvexSpecialFields(tables) {
70
+ return Object.fromEntries(
71
+ Object.entries(tables).map(([key, table]) => {
72
+ const fields = Object.fromEntries(
73
+ Object.entries(table.fields).map(([fieldKey, field]) => [
74
+ field.fieldName ?? fieldKey,
75
+ {
76
+ ...field.sortable ? { sortable: true } : {},
77
+ ...field.unique ? { unique: true } : {},
78
+ ...field.references ? { references: field.references } : {}
79
+ }
80
+ ]).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true)
81
+ );
82
+ return [key, fields];
83
+ }).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true)
84
+ );
85
+ }
86
+ function getMergedConvexIndexFields(tables) {
87
+ return Object.fromEntries(
88
+ Object.entries(tables).map(([key, table]) => {
89
+ const manualIndexes = convexIndexFields[key]?.map((index) => {
90
+ return typeof index === "string" ? table.fields[index]?.fieldName ?? index : index.map((i) => table.fields[i]?.fieldName ?? i);
91
+ }) || [];
92
+ const specialFieldsObj = getConvexSpecialFields(tables);
93
+ const specialFieldIndexes = Object.keys(specialFieldsObj[key] || {}).filter(
94
+ (index) => !manualIndexes.some((m) => Array.isArray(m) ? m[0] === index : m === index)
95
+ );
96
+ return [key, manualIndexes.concat(specialFieldIndexes)];
97
+ })
98
+ );
99
+ }
100
+ async function generateConvexSchema(authOptions) {
101
+ const tables = getAuthTables(authOptions);
102
+ let code = `/**
103
+ * Auto-generated Better Auth tables for Convex.
104
+ * Import these tables in your convex/schema.ts:
105
+ *
106
+ * import { defineSchema } from 'convex/server'
107
+ * import { authTables } from './_generated/auth-tables'
108
+ *
109
+ * export default defineSchema({ ...authTables, ...yourTables })
110
+ */
111
+
112
+ import { defineTable } from 'convex/server'
113
+ import { v } from 'convex/values'
114
+
115
+ export const authTables = {
116
+ `;
117
+ const getType = (_name, field) => {
118
+ const type = field.type;
119
+ const typeMap = {
120
+ "string": "v.string()",
121
+ "boolean": "v.boolean()",
122
+ "number": "v.number()",
123
+ "date": "v.number()",
124
+ "json": "v.string()",
125
+ "number[]": "v.array(v.number())",
126
+ "string[]": "v.array(v.string())"
127
+ };
128
+ return typeMap[type];
129
+ };
130
+ for (const tableKey in tables) {
131
+ const table = tables[tableKey];
132
+ const modelName = table.modelName;
133
+ const fields = Object.fromEntries(Object.entries(table.fields).filter(([key]) => key !== "id"));
134
+ const indexes = getMergedConvexIndexFields(tables)[tableKey]?.map((index) => {
135
+ const indexArray = Array.isArray(index) ? index.sort() : [index];
136
+ const indexName = indexArray.join("_");
137
+ return `.index('${indexName}', ${JSON.stringify(indexArray)})`;
138
+ }) || [];
139
+ const schema = `${modelName}: defineTable({
140
+ ${Object.keys(fields).map((field) => {
141
+ const attr = fields[field];
142
+ const type = getType(field, attr);
143
+ const optional = (fieldSchema) => attr.required ? fieldSchema : `v.optional(v.union(v.null(), ${fieldSchema}))`;
144
+ return ` ${attr.fieldName ?? field}: ${optional(type)},`;
145
+ }).join("\n")}
146
+ })${indexes.length > 0 ? `
147
+ ${indexes.join("\n ")}` : ""},
148
+ `;
149
+ code += ` ${schema}`;
150
+ }
151
+ code += `}
152
+ `;
153
+ return code;
154
+ }
55
155
  async function loadUserAuthConfig(configPath, throwOnError = false) {
56
156
  const { createJiti } = await import('jiti');
57
157
  const { defineServerAuth } = await import('../dist/runtime/config.js');
@@ -63,11 +163,11 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
63
163
  globalThis.defineServerAuth._count++;
64
164
  try {
65
165
  const mod = await jiti.import(configPath);
66
- const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
166
+ const configFn = mod.default;
67
167
  if (typeof configFn === "function") {
68
168
  return configFn({ runtimeConfig: {}, db: null });
69
169
  }
70
- consola$1.warn("[@onmax/nuxt-better-auth] auth.config.ts does not export a function. Expected: export default defineServerAuth(...)");
170
+ consola$1.warn("[@onmax/nuxt-better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)");
71
171
  if (throwOnError) {
72
172
  throw new Error("auth.config.ts must export default defineServerAuth(...)");
73
173
  }
@@ -101,8 +201,85 @@ function getHubCasing(hub) {
101
201
  return hub.db.casing;
102
202
  }
103
203
  const consola = consola$1.withTag("nuxt-better-auth");
204
+ function resolveConvexUrl(nuxt) {
205
+ const convexConfig = nuxt.options.convex;
206
+ if (convexConfig?.url)
207
+ return convexConfig.url;
208
+ const runtimeUrl = nuxt.options.runtimeConfig.public?.convex?.url;
209
+ if (runtimeUrl)
210
+ return runtimeUrl;
211
+ if (process.env.CONVEX_URL)
212
+ return process.env.CONVEX_URL;
213
+ if (process.env.NUXT_PUBLIC_CONVEX_URL)
214
+ return process.env.NUXT_PUBLIC_CONVEX_URL;
215
+ return "";
216
+ }
217
+ const generateSecret = () => randomBytes(32).toString("hex");
218
+ function readEnvFile(rootDir) {
219
+ const envPath = join(rootDir, ".env");
220
+ return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
221
+ }
222
+ function hasEnvSecret(rootDir) {
223
+ const match = readEnvFile(rootDir).match(/^BETTER_AUTH_SECRET=(.+)$/m);
224
+ return !!match && !!match[1] && match[1].trim().length > 0;
225
+ }
226
+ function appendSecretToEnv(rootDir, secret) {
227
+ const envPath = join(rootDir, ".env");
228
+ let content = readEnvFile(rootDir);
229
+ if (content.length > 0 && !content.endsWith("\n"))
230
+ content += "\n";
231
+ content += `BETTER_AUTH_SECRET=${secret}
232
+ `;
233
+ writeFileSync(envPath, content, "utf-8");
234
+ }
235
+ async function promptForSecret(rootDir) {
236
+ if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
237
+ return void 0;
238
+ if (isCI || isTest) {
239
+ const secret2 = generateSecret();
240
+ appendSecretToEnv(rootDir, secret2);
241
+ consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI mode)");
242
+ return secret2;
243
+ }
244
+ consola.box("BETTER_AUTH_SECRET is required for authentication.\nThis will be appended to your .env file.");
245
+ const choice = await consola.prompt("How do you want to set it?", {
246
+ type: "select",
247
+ options: [
248
+ { label: "Generate for me", value: "generate", hint: "uses crypto.randomBytes(32)" },
249
+ { label: "Enter manually", value: "paste" },
250
+ { label: "Skip", value: "skip", hint: "will fail in production" }
251
+ ],
252
+ cancel: "null"
253
+ });
254
+ if (typeof choice === "symbol" || choice === "skip") {
255
+ consola.warn("Skipping BETTER_AUTH_SECRET. Auth will fail without it in production.");
256
+ return void 0;
257
+ }
258
+ let secret;
259
+ if (choice === "generate") {
260
+ secret = generateSecret();
261
+ } else {
262
+ const input = await consola.prompt("Paste your secret (min 32 chars):", { type: "text", cancel: "null" });
263
+ if (typeof input === "symbol" || !input || input.length < 32) {
264
+ consola.warn("Invalid secret. Skipping.");
265
+ return void 0;
266
+ }
267
+ secret = input;
268
+ }
269
+ const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
270
+ const confirm = await consola.prompt(`Add to .env:
271
+ BETTER_AUTH_SECRET=${preview}
272
+ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
273
+ if (typeof confirm === "symbol" || !confirm) {
274
+ consola.info("Cancelled. Secret not written.");
275
+ return void 0;
276
+ }
277
+ appendSecretToEnv(rootDir, secret);
278
+ consola.success("Added BETTER_AUTH_SECRET to .env");
279
+ return secret;
280
+ }
104
281
  const module$1 = defineNuxtModule({
105
- meta: { name: "@onmax/nuxt-better-auth", configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
282
+ meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
106
283
  defaults: {
107
284
  clientOnly: false,
108
285
  serverConfig: "server/auth.config",
@@ -110,8 +287,40 @@ const module$1 = defineNuxtModule({
110
287
  redirects: { login: "/login", guest: "/" },
111
288
  secondaryStorage: false
112
289
  },
290
+ async onInstall(nuxt) {
291
+ const generatedSecret = await promptForSecret(nuxt.options.rootDir);
292
+ if (generatedSecret)
293
+ process.env.BETTER_AUTH_SECRET = generatedSecret;
294
+ const serverPath = join(nuxt.options.rootDir, "server/auth.config.ts");
295
+ const clientPath = join(nuxt.options.srcDir, "auth.config.ts");
296
+ const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
297
+
298
+ export default defineServerAuth({
299
+ emailAndPassword: { enabled: true },
300
+ })
301
+ `;
302
+ const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
303
+
304
+ export default defineClientAuth({})
305
+ `;
306
+ if (!existsSync(serverPath)) {
307
+ await mkdir(dirname(serverPath), { recursive: true });
308
+ await writeFile(serverPath, serverTemplate);
309
+ consola.success("Created server/auth.config.ts");
310
+ }
311
+ if (!existsSync(clientPath)) {
312
+ await mkdir(dirname(clientPath), { recursive: true });
313
+ await writeFile(clientPath, clientTemplate);
314
+ const relativePath = clientPath.replace(`${nuxt.options.rootDir}/`, "");
315
+ consola.success(`Created ${relativePath}`);
316
+ }
317
+ },
113
318
  async setup(options, nuxt) {
114
319
  const resolver = createResolver(import.meta.url);
320
+ if (options.clientConfig === "app/auth.config") {
321
+ const srcDirRelative = nuxt.options.srcDir.replace(`${nuxt.options.rootDir}/`, "");
322
+ options.clientConfig = srcDirRelative === nuxt.options.srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
323
+ }
115
324
  const clientOnly = options.clientOnly;
116
325
  const serverConfigFile = options.serverConfig;
117
326
  const clientConfigFile = options.clientConfig;
@@ -120,12 +329,18 @@ const module$1 = defineNuxtModule({
120
329
  const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
121
330
  const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
122
331
  if (!clientOnly && !serverConfigExists)
123
- throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - create with defineServerAuth()`);
332
+ throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
124
333
  if (!clientConfigExists)
125
- throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export createAppAuthClient()`);
334
+ throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
126
335
  const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
127
336
  const hub = hasNuxtHub ? nuxt.options.hub : void 0;
128
337
  const hasHubDb = !clientOnly && hasNuxtHub && !!hub?.db;
338
+ const hasConvex = hasNuxtModule("nuxt-convex", nuxt);
339
+ const convexUrl = resolveConvexUrl(nuxt);
340
+ const hasConvexDb = !clientOnly && hasConvex && !!convexUrl && !hasHubDb;
341
+ if (hasConvexDb) {
342
+ consola.info("Detected Convex - using Convex HTTP adapter for Better Auth database");
343
+ }
129
344
  let secondaryStorageEnabled = options.secondaryStorage ?? false;
130
345
  if (secondaryStorageEnabled && clientOnly) {
131
346
  consola.warn("secondaryStorage is not available in clientOnly mode. Disabling.");
@@ -137,7 +352,7 @@ const module$1 = defineNuxtModule({
137
352
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
138
353
  nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
139
354
  redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
140
- useDatabase: hasHubDb,
355
+ useDatabase: hasHubDb || hasConvexDb,
141
356
  clientOnly
142
357
  });
143
358
  if (clientOnly) {
@@ -151,7 +366,7 @@ const module$1 = defineNuxtModule({
151
366
  const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
152
367
  nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
153
368
  const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
154
- if (!nuxt.options.dev && !betterAuthSecret) {
369
+ if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
155
370
  throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
156
371
  }
157
372
  if (betterAuthSecret && betterAuthSecret.length < 32) {
@@ -183,13 +398,36 @@ export function createSecondaryStorage() {
183
398
  throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
184
399
  }
185
400
  const hubDialect = getHubDialect(hub) ?? "sqlite";
186
- const databaseCode = hasHubDb ? `import { db, schema } from '../hub/db.mjs'
401
+ const usePlural = options.schema?.usePlural ?? false;
402
+ const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
403
+ let databaseCode;
404
+ if (hasHubDb) {
405
+ databaseCode = `import { db, schema } from '../hub/db.mjs'
187
406
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
188
407
  const rawDialect = '${hubDialect}'
189
408
  const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
190
- export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
191
- export { db }` : `export function createDatabase() { return undefined }
409
+ export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${usePlural}, camelCase: ${camelCase} }) }
410
+ export { db }`;
411
+ } else if (hasConvexDb) {
412
+ nuxt.options.runtimeConfig.betterAuth = defu(
413
+ nuxt.options.runtimeConfig.betterAuth || {},
414
+ { convexUrl }
415
+ );
416
+ databaseCode = `import { useRuntimeConfig } from '#imports'
417
+ import { createConvexHttpAdapter } from '@onmax/nuxt-better-auth/adapters/convex'
418
+ import { api } from '#convex/api'
419
+
420
+ export function createDatabase() {
421
+ const config = useRuntimeConfig()
422
+ const convexUrl = config.betterAuth?.convexUrl || config.public?.convex?.url
423
+ if (!convexUrl) throw new Error('[nuxt-better-auth] CONVEX_URL not configured')
424
+ return createConvexHttpAdapter({ url: convexUrl, api: api.auth })
425
+ }
426
+ export const db = undefined`;
427
+ } else {
428
+ databaseCode = `export function createDatabase() { return undefined }
192
429
  export const db = undefined`;
430
+ }
193
431
  const databaseTemplate = addTemplate({ filename: "better-auth/database.mjs", getContents: () => databaseCode, write: true });
194
432
  nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
195
433
  addTypeTemplate({
@@ -220,9 +458,9 @@ declare module '#auth/database' {
220
458
  getContents: () => `
221
459
  import type { InferUser, InferSession, InferPluginTypes } from 'better-auth'
222
460
  import type { RuntimeConfig } from 'nuxt/schema'
223
- import type configFn from '${serverConfigPath}'
461
+ import type createServerAuth from '${serverConfigPath}'
224
462
 
225
- type _Config = ReturnType<typeof configFn>
463
+ type _Config = ReturnType<typeof createServerAuth>
226
464
 
227
465
  declare module '#nuxt-better-auth' {
228
466
  interface AuthUser extends InferUser<_Config> {}
@@ -243,7 +481,7 @@ interface _AugmentedServerAuthContext {
243
481
  declare module '@onmax/nuxt-better-auth/config' {
244
482
  import type { BetterAuthOptions } from 'better-auth'
245
483
  type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>
246
- export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => T): (ctx: _AugmentedServerAuthContext) => T
484
+ export function defineServerAuth<T extends ServerAuthConfig>(config: T | ((ctx: _AugmentedServerAuthContext) => T)): (ctx: _AugmentedServerAuthContext) => T
247
485
  }
248
486
  `
249
487
  }, { nuxt: true, nitro: true, node: true });
@@ -280,7 +518,7 @@ export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, RequireSessionOptio
280
518
  addTypeTemplate({
281
519
  filename: "types/nuxt-better-auth-client.d.ts",
282
520
  getContents: () => `
283
- import type { createAppAuthClient } from '${clientConfigPath}'
521
+ import type createAppAuthClient from '${clientConfigPath}'
284
522
  declare module '#nuxt-better-auth' {
285
523
  export type AppAuthClient = ReturnType<typeof createAppAuthClient>
286
524
  }
@@ -309,8 +547,13 @@ declare module '#nuxt-better-auth' {
309
547
  if (hasHubDb) {
310
548
  await setupBetterAuthSchema(nuxt, serverConfigPath, options);
311
549
  }
550
+ if (hasConvexDb) {
551
+ await setupConvexAuthSchema(nuxt, serverConfigPath);
552
+ }
312
553
  const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
313
554
  if (!isProduction && !clientOnly) {
555
+ if (!hasNuxtModule("@nuxt/ui"))
556
+ await installModule("@nuxt/ui");
314
557
  setupDevTools(nuxt);
315
558
  addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
316
559
  if (hasHubDb) {
@@ -387,5 +630,26 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
387
630
  }
388
631
  });
389
632
  }
633
+ async function setupConvexAuthSchema(nuxt, serverConfigPath) {
634
+ const isProduction = !nuxt.options.dev;
635
+ try {
636
+ const configFile = `${serverConfigPath}.ts`;
637
+ const userConfig = await loadUserAuthConfig(configFile, isProduction);
638
+ const authOptions = { ...userConfig };
639
+ const schemaCode = await generateConvexSchema(authOptions);
640
+ const schemaDir = join(nuxt.options.buildDir, "better-auth");
641
+ const schemaPath = join(schemaDir, "auth-tables.convex.ts");
642
+ await mkdir(schemaDir, { recursive: true });
643
+ await writeFile(schemaPath, schemaCode);
644
+ addTemplate({ filename: "better-auth/auth-tables.convex.ts", getContents: () => schemaCode, write: true });
645
+ nuxt.options.alias["#auth/convex-schema"] = schemaPath;
646
+ consola.info("Generated Convex auth schema at .nuxt/better-auth/auth-tables.convex.ts");
647
+ } catch (error) {
648
+ if (isProduction) {
649
+ throw error;
650
+ }
651
+ consola.error("Failed to generate Convex schema:", error);
652
+ }
653
+ }
390
654
 
391
655
  export { module$1 as default };
@@ -0,0 +1,111 @@
1
+ import type { FunctionReference } from 'convex/server';
2
+ interface ConvexCleanedWhere {
3
+ field: string;
4
+ value: string | number | boolean | string[] | number[] | null;
5
+ operator?: 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'in' | 'not_in' | 'ne' | 'contains' | 'starts_with' | 'ends_with';
6
+ connector?: 'AND' | 'OR';
7
+ }
8
+ interface PaginationResult<T> {
9
+ page: T[];
10
+ isDone: boolean;
11
+ continueCursor: string | null;
12
+ splitCursor?: string;
13
+ pageStatus?: 'SplitRecommended' | 'SplitRequired' | string;
14
+ count?: number;
15
+ }
16
+ interface ConvexAuthApi {
17
+ create: FunctionReference<'mutation', 'public', {
18
+ input: {
19
+ model: string;
20
+ data: Record<string, unknown>;
21
+ };
22
+ select?: string[];
23
+ }, unknown>;
24
+ findOne: FunctionReference<'query', 'public', {
25
+ model: string;
26
+ where?: ConvexCleanedWhere[];
27
+ select?: string[];
28
+ }, unknown>;
29
+ findMany: FunctionReference<'query', 'public', {
30
+ model: string;
31
+ where?: ConvexCleanedWhere[];
32
+ limit?: number;
33
+ sortBy?: {
34
+ direction: 'asc' | 'desc';
35
+ field: string;
36
+ };
37
+ paginationOpts: {
38
+ numItems: number;
39
+ cursor: string | null;
40
+ };
41
+ }, PaginationResult<unknown>>;
42
+ updateOne: FunctionReference<'mutation', 'public', {
43
+ input: {
44
+ model: string;
45
+ where?: ConvexCleanedWhere[];
46
+ update: Record<string, unknown>;
47
+ };
48
+ }, unknown>;
49
+ updateMany: FunctionReference<'mutation', 'public', {
50
+ input: {
51
+ model: string;
52
+ where?: ConvexCleanedWhere[];
53
+ update: Record<string, unknown>;
54
+ };
55
+ paginationOpts: {
56
+ numItems: number;
57
+ cursor: string | null;
58
+ };
59
+ }, PaginationResult<unknown> & {
60
+ count: number;
61
+ }>;
62
+ deleteOne: FunctionReference<'mutation', 'public', {
63
+ input: {
64
+ model: string;
65
+ where?: ConvexCleanedWhere[];
66
+ };
67
+ }, unknown>;
68
+ deleteMany: FunctionReference<'mutation', 'public', {
69
+ input: {
70
+ model: string;
71
+ where?: ConvexCleanedWhere[];
72
+ };
73
+ paginationOpts: {
74
+ numItems: number;
75
+ cursor: string | null;
76
+ };
77
+ }, PaginationResult<unknown> & {
78
+ count: number;
79
+ }>;
80
+ }
81
+ export interface ConvexHttpAdapterOptions {
82
+ /** Convex deployment URL (e.g., https://your-app.convex.cloud) */
83
+ url: string;
84
+ /** Convex API functions for auth operations - import from your convex/_generated/api */
85
+ api: ConvexAuthApi;
86
+ /** Enable debug logging for adapter operations */
87
+ debugLogs?: boolean;
88
+ }
89
+ /**
90
+ * Creates a Better Auth adapter that communicates with Convex via HTTP.
91
+ * Uses ConvexHttpClient for server-side auth operations.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { api } from '~/convex/_generated/api'
96
+ *
97
+ * export default defineServerAuth(() => ({
98
+ * database: createConvexHttpAdapter({
99
+ * url: process.env.CONVEX_URL!,
100
+ * api: api.auth,
101
+ * }),
102
+ * }))
103
+ * ```
104
+ *
105
+ * @limitations
106
+ * - `update()` only supports AND-connected where clauses (no OR support)
107
+ * - `count()` fetches all documents client-side (Convex limitation)
108
+ * - `offset` pagination not supported in `findMany()`
109
+ */
110
+ export declare function createConvexHttpAdapter(options: ConvexHttpAdapterOptions): import("better-auth/adapters").AdapterFactory;
111
+ export {};