diff --git a/src/runtime/config.ts b/src/runtime/config.ts
index 67ca804..75d49c6 100644
--- a/src/runtime/config.ts
+++ b/src/runtime/config.ts
@@ -1,6 +1,5 @@
import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'
import type { BetterAuthClientOptions } from 'better-auth/client'
-import type { DatabaseProvider } from '../database-provider'
import type { CasingOption } from '../schema-generator'
import type { ServerAuthContext } from './types/augment'
import { createAuthClient } from 'better-auth/vue'
@@ -12,13 +11,16 @@ export interface ClientAuthContext {
siteUrl: string
}
-export type ServerAuthConfig = Omit
& {
+export type ServerAuthConfig = Omit & {
plugins?: readonly BetterAuthPlugin[]
}
export type ClientAuthConfig = Omit & { baseURL?: string }
export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig
export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig
+export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {})
+export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId
+export type DatabaseSource = 'module' | 'user'
// Module options for nuxt.config.ts
export interface BetterAuthModuleOptions {
@@ -44,13 +46,6 @@ export interface BetterAuthModuleOptions {
}
/** Enable KV secondary storage for sessions. Requires hub.kv: true */
secondaryStorage?: boolean
- /** Database backend selection and provider-specific options */
- database?: {
- /** Explicit database provider. Default: auto (nuxthub when available, otherwise none) */
- provider?: DatabaseProvider
- /** Convex deployment URL override (highest priority for Convex provider) */
- convexUrl?: string
- }
/** Schema generation options. Must match drizzleAdapter config. */
schema?: {
/** Plural table names: user → users. Default: false */
@@ -64,7 +59,8 @@ export interface BetterAuthModuleOptions {
export interface AuthRuntimeConfig {
redirects: { login: string, guest: string }
useDatabase: boolean
- databaseProvider: DatabaseProvider
+ databaseProvider: EffectiveDatabaseProviderId
+ databaseSource: DatabaseSource
clientOnly: boolean
session: { skipHydratedSsrGetSession: boolean }
}
diff --git a/src/runtime/server/api/_better-auth/accounts.get.ts b/src/runtime/server/api/_better-auth/accounts.get.ts
index f7fe383..c4ac9e7 100644
--- a/src/runtime/server/api/_better-auth/accounts.get.ts
+++ b/src/runtime/server/api/_better-auth/accounts.get.ts
@@ -1,8 +1,20 @@
import { defineEventHandler, getQuery } from 'h3'
+import { isDevtoolsDatabaseEligible } from '../../../utils/devtools-database'
+import { getDatabaseProvider, getDatabaseSource } from '../../utils/auth'
import { paginationQuerySchema, sanitizeSearchPattern } from './_schema'
export default defineEventHandler(async (event) => {
try {
+ const databaseProvider = getDatabaseProvider()
+ const databaseSource = getDatabaseSource()
+ if (!isDevtoolsDatabaseEligible({ databaseProvider, databaseSource })) {
+ return {
+ accounts: [],
+ total: 0,
+ error: 'DevTools DB routes are only available for module-managed nuxthub database mode.',
+ }
+ }
+
const { db, schema } = await import('@nuxthub/db')
if (!schema.account)
return { accounts: [], total: 0, error: 'Account table not found' }
diff --git a/src/runtime/server/api/_better-auth/config.get.ts b/src/runtime/server/api/_better-auth/config.get.ts
index 7fceb8d..e865859 100644
--- a/src/runtime/server/api/_better-auth/config.get.ts
+++ b/src/runtime/server/api/_better-auth/config.get.ts
@@ -1,6 +1,6 @@
import { defineEventHandler } from 'h3'
import { useRuntimeConfig } from 'nitropack/runtime'
-import { serverAuth } from '../../utils/auth'
+import { getDatabaseProvider, getDatabaseSource, serverAuth } from '../../utils/auth'
export default defineEventHandler(async (event) => {
try {
@@ -8,8 +8,10 @@ export default defineEventHandler(async (event) => {
const options = auth.options
const authContext = await ((auth as { $context?: Promise<{ trustedOrigins?: string[] }> | { trustedOrigins?: string[] } }).$context)
const runtimeConfig = useRuntimeConfig()
- const publicAuth = runtimeConfig.public?.auth as { redirects?: { login?: string, guest?: string }, useDatabase?: boolean, databaseProvider?: 'none' | 'nuxthub' | 'convex' } | undefined
+ const publicAuth = runtimeConfig.public?.auth as { redirects?: { login?: string, guest?: string } } | undefined
const privateAuth = runtimeConfig.auth as { secondaryStorage?: boolean } | undefined
+ const databaseProvider = getDatabaseProvider()
+ const databaseSource = getDatabaseSource()
const configuredTrustedOrigins = Array.isArray(options.trustedOrigins) ? options.trustedOrigins : []
const effectiveTrustedOrigins = authContext?.trustedOrigins || configuredTrustedOrigins
@@ -24,8 +26,9 @@ export default defineEventHandler(async (event) => {
module: {
redirects: publicAuth?.redirects || { login: '/login', guest: '/' },
secondaryStorage: privateAuth?.secondaryStorage ?? false,
- useDatabase: publicAuth?.useDatabase ?? false,
- databaseProvider: publicAuth?.databaseProvider ?? 'none',
+ useDatabase: databaseProvider !== 'none',
+ databaseProvider,
+ databaseSource,
},
// Server config (server/auth.config.ts)
server: {
diff --git a/src/runtime/server/api/_better-auth/sessions.get.ts b/src/runtime/server/api/_better-auth/sessions.get.ts
index a77ff98..bc4bff8 100644
--- a/src/runtime/server/api/_better-auth/sessions.get.ts
+++ b/src/runtime/server/api/_better-auth/sessions.get.ts
@@ -1,8 +1,20 @@
import { defineEventHandler, getQuery } from 'h3'
+import { isDevtoolsDatabaseEligible } from '../../../utils/devtools-database'
+import { getDatabaseProvider, getDatabaseSource } from '../../utils/auth'
import { paginationQuerySchema, sanitizeSearchPattern } from './_schema'
export default defineEventHandler(async (event) => {
try {
+ const databaseProvider = getDatabaseProvider()
+ const databaseSource = getDatabaseSource()
+ if (!isDevtoolsDatabaseEligible({ databaseProvider, databaseSource })) {
+ return {
+ sessions: [],
+ total: 0,
+ error: 'DevTools DB routes are only available for module-managed nuxthub database mode.',
+ }
+ }
+
const { db, schema } = await import('@nuxthub/db')
if (!schema.session)
return { sessions: [], total: 0, error: 'Session table not found' }
diff --git a/src/runtime/server/api/_better-auth/users.get.ts b/src/runtime/server/api/_better-auth/users.get.ts
index 63adb19..9721fe0 100644
--- a/src/runtime/server/api/_better-auth/users.get.ts
+++ b/src/runtime/server/api/_better-auth/users.get.ts
@@ -1,8 +1,20 @@
import { defineEventHandler, getQuery } from 'h3'
+import { isDevtoolsDatabaseEligible } from '../../../utils/devtools-database'
+import { getDatabaseProvider, getDatabaseSource } from '../../utils/auth'
import { paginationQuerySchema, sanitizeSearchPattern } from './_schema'
export default defineEventHandler(async (event) => {
try {
+ const databaseProvider = getDatabaseProvider()
+ const databaseSource = getDatabaseSource()
+ if (!isDevtoolsDatabaseEligible({ databaseProvider, databaseSource })) {
+ return {
+ users: [],
+ total: 0,
+ error: 'DevTools DB routes are only available for module-managed nuxthub database mode.',
+ }
+ }
+
const { db, schema } = await import('@nuxthub/db')
if (!schema.user)
return { users: [], total: 0, error: 'User table not found' }
diff --git a/src/runtime/server/utils/auth.ts b/src/runtime/server/utils/auth.ts
index 293178c..5e0ad47 100644
--- a/src/runtime/server/utils/auth.ts
+++ b/src/runtime/server/utils/auth.ts
@@ -1,5 +1,6 @@
import type { Auth } from 'better-auth'
import type { H3Event } from 'h3'
+import type { EffectiveDatabaseProviderId, ModuleDatabaseProviderId } from '../../config'
import { createDatabase, db } from '#auth/database'
import { createSecondaryStorage } from '#auth/secondary-storage'
import createServerAuth from '#auth/server'
@@ -12,6 +13,8 @@ type AuthInstance = Auth>
let _auth: AuthInstance | null = null
let _baseURLInferenceLogged = false
+let _databaseSource: 'module' | 'user' = 'module'
+let _databaseProvider: EffectiveDatabaseProviderId = 'none'
function normalizeLoopbackOrigin(origin: string): string {
if (!import.meta.dev)
@@ -151,9 +154,14 @@ export function serverAuth(event?: H3Event): AuthInstance {
const runtimeConfig = useRuntimeConfig()
const siteUrl = getBaseURL(event)
+ const moduleDatabaseProvider = (
+ (runtimeConfig.public?.auth as { databaseProvider?: ModuleDatabaseProviderId } | undefined)?.databaseProvider
+ ) ?? 'none'
- const database = createDatabase()
const userConfig = createServerAuth({ runtimeConfig, db })
+ const database = userConfig.database ?? createDatabase()
+ _databaseSource = userConfig.database ? 'user' : 'module'
+ _databaseProvider = userConfig.database ? 'user' : moduleDatabaseProvider
_auth = betterAuth({
...userConfig,
@@ -165,3 +173,11 @@ export function serverAuth(event?: H3Event): AuthInstance {
return _auth
}
+
+export function getDatabaseSource(): 'module' | 'user' {
+ return _databaseSource
+}
+
+export function getDatabaseProvider(): EffectiveDatabaseProviderId {
+ return _databaseProvider
+}
diff --git a/src/runtime/types/augment.ts b/src/runtime/types/augment.ts
index b17a14e..8a8e92b 100644
--- a/src/runtime/types/augment.ts
+++ b/src/runtime/types/augment.ts
@@ -44,5 +44,5 @@ export interface UserSessionComposable {
fetchSession: (options?: { headers?: HeadersInit, force?: boolean }) => Promise
waitForSession: () => Promise
signOut: (options?: { onSuccess?: () => void | Promise }) => Promise
- updateUser: (updates: Partial) => void
+ updateUser: (updates: Partial) => Promise
}
diff --git a/src/runtime/utils/devtools-database.ts b/src/runtime/utils/devtools-database.ts
new file mode 100644
index 0000000..eef00e5
--- /dev/null
+++ b/src/runtime/utils/devtools-database.ts
@@ -0,0 +1,14 @@
+export interface DevtoolsDatabaseEligibilityInput {
+ databaseProvider?: string | null
+ databaseSource?: string | null
+ fallbackModuleProvider?: string | null
+}
+
+export function isDevtoolsDatabaseEligible(input: DevtoolsDatabaseEligibilityInput): boolean {
+ const { databaseProvider, databaseSource, fallbackModuleProvider } = input
+
+ if (typeof databaseProvider === 'string')
+ return databaseProvider === 'nuxthub' && databaseSource === 'module'
+
+ return fallbackModuleProvider === 'nuxthub'
+}
diff --git a/src/schema-generator.ts b/src/schema-generator.ts
index fdf7ba2..4da5519 100644
--- a/src/schema-generator.ts
+++ b/src/schema-generator.ts
@@ -1,8 +1,6 @@
import type { DBAdapter } from '@better-auth/cli/api'
import type { BetterAuthOptions } from 'better-auth'
-import type { BetterAuthDBSchema, DBFieldAttribute } from 'better-auth/db'
import { generateDrizzleSchema as _generateDrizzleSchema } from '@better-auth/cli/api'
-import { getAuthTables } from 'better-auth/db'
import { consola } from 'consola'
export type CasingOption = 'camelCase' | 'snake_case'
@@ -59,125 +57,6 @@ declare global {
var defineServerAuth: DefineServerAuthFn | undefined
}
-// Index fields for Convex schema
-// Source: @convex-dev/better-auth (https://labs.convex.dev/better-auth)
-// Keep in sync with upstream when updating convex-dev/better-auth dependency
-const convexIndexFields: Record = {
- account: ['accountId', ['accountId', 'providerId'], ['providerId', 'userId']],
- rateLimit: ['key'],
- session: ['expiresAt', ['expiresAt', 'userId']],
- verification: ['expiresAt', 'identifier'],
- user: [['email', 'name'], 'name', 'userId'],
- passkey: ['credentialID'],
- oauthConsent: [['clientId', 'userId']],
-}
-
-function getConvexSpecialFields(tables: BetterAuthDBSchema) {
- return Object.fromEntries(
- Object.entries(tables)
- .map(([key, table]) => {
- const fields = Object.fromEntries(
- Object.entries(table.fields)
- .map(([fieldKey, field]) => [
- field.fieldName ?? fieldKey,
- {
- ...(field.sortable ? { sortable: true } : {}),
- ...(field.unique ? { unique: true } : {}),
- ...(field.references ? { references: field.references } : {}),
- },
- ])
- .filter(([_key, value]) => typeof value === 'object' ? Object.keys(value).length > 0 : true),
- )
- return [key, fields]
- })
- .filter(([_key, value]) => typeof value === 'object' ? Object.keys(value).length > 0 : true),
- )
-}
-
-function getMergedConvexIndexFields(tables: BetterAuthDBSchema) {
- return Object.fromEntries(
- Object.entries(tables).map(([key, table]) => {
- const manualIndexes = convexIndexFields[key]?.map((index) => {
- return typeof index === 'string'
- ? (table.fields[index]?.fieldName ?? index)
- : index.map(i => table.fields[i]?.fieldName ?? i)
- }) || []
- const specialFieldsObj = getConvexSpecialFields(tables)
- const specialFieldIndexes = Object.keys(specialFieldsObj[key] || {}).filter(
- index => !manualIndexes.some(m => Array.isArray(m) ? m[0] === index : m === index),
- )
- return [key, manualIndexes.concat(specialFieldIndexes)]
- }),
- )
-}
-
-export async function generateConvexSchema(authOptions: BetterAuthOptions): Promise {
- const tables = getAuthTables(authOptions)
-
- let code = `/**
- * Auto-generated Better Auth tables for Convex.
- * Generated at: .nuxt/better-auth/auth-tables.convex.ts
- * Import these tables in your convex/schema.ts:
- *
- * import { defineSchema } from 'convex/server'
- * import { authTables } from './path-to-auth-tables.convex'
- *
- * export default defineSchema({ ...authTables, ...yourTables })
- */
-
-import { defineTable } from 'convex/server'
-import { v } from 'convex/values'
-
-export const authTables = {
-`
-
- const getType = (_name: string, field: DBFieldAttribute): string => {
- const type = field.type as 'string' | 'number' | 'boolean' | 'date' | 'json' | 'string[]' | 'number[]'
- const typeMap: Record = {
- 'string': 'v.string()',
- 'boolean': 'v.boolean()',
- 'number': 'v.number()',
- 'date': 'v.number()',
- 'json': 'v.string()',
- 'number[]': 'v.array(v.number())',
- 'string[]': 'v.array(v.string())',
- }
- return typeMap[type]
- }
-
- for (const tableKey in tables) {
- const table = tables[tableKey]!
- const modelName = table.modelName
-
- // No id fields in Convex schema
- const fields = Object.fromEntries(Object.entries(table.fields).filter(([key]) => key !== 'id'))
-
- const indexes = getMergedConvexIndexFields(tables)[tableKey]?.map((index) => {
- const indexArray = Array.isArray(index) ? index.sort() : [index]
- const indexName = indexArray.join('_')
- return `.index('${indexName}', ${JSON.stringify(indexArray)})`
- }) || []
-
- const schema = `${modelName}: defineTable({
-${Object.keys(fields)
- .map((field) => {
- const attr = fields[field]!
- const type = getType(field, attr as DBFieldAttribute)
- const optional = (fieldSchema: string) =>
- attr.required ? fieldSchema : `v.optional(v.union(v.null(), ${fieldSchema}))`
- return ` ${attr.fieldName ?? field}: ${optional(type)},`
- })
- .join('\n')}
- })${indexes.length > 0 ? `\n ${indexes.join('\n ')}` : ''},\n`
- code += ` ${schema}`
- }
-
- code += `}
-`
-
- return code
-}
-
export async function loadUserAuthConfig(configPath: string, throwOnError = false): Promise> {
const { createJiti } = await import('jiti')
const { defineServerAuth } = await import('./runtime/config')
diff --git a/src/types/hooks.ts b/src/types/hooks.ts
index a693c1a..6dfc69f 100644
--- a/src/types/hooks.ts
+++ b/src/types/hooks.ts
@@ -1,4 +1,30 @@
+import type { Nuxt } from '@nuxt/schema'
import type { BetterAuthOptions } from 'better-auth'
+import type { DbDialect } from '../module/hub'
+import type { BetterAuthModuleOptions } from '../runtime/config'
+
+export interface BetterAuthDatabaseProviderBuildContext {
+ hubDialect: DbDialect
+ usePlural: boolean
+ camelCase: boolean
+}
+
+export interface BetterAuthDatabaseProviderSetupContext {
+ nuxt: Nuxt
+ options: BetterAuthModuleOptions
+ clientOnly: boolean
+}
+
+export interface BetterAuthDatabaseProviderEnabledContext extends BetterAuthDatabaseProviderSetupContext {
+ hasHubDbAvailable: boolean
+}
+
+export interface BetterAuthDatabaseProviderDefinition {
+ buildDatabaseCode: (ctx: BetterAuthDatabaseProviderBuildContext) => string
+ setup?: (ctx: BetterAuthDatabaseProviderSetupContext) => void | Promise
+ isEnabled?: (ctx: BetterAuthDatabaseProviderEnabledContext) => boolean
+ priority?: number
+}
declare module '@nuxt/schema' {
interface NuxtHooks {
@@ -8,5 +34,11 @@ declare module '@nuxt/schema' {
* @param config - Partial config to merge into the auth options
*/
'better-auth:config:extend': (config: Partial) => void | Promise
+
+ /**
+ * Register or override Better Auth database providers.
+ * Providers are auto-selected via `isEnabled` + `priority`.
+ */
+ 'better-auth:database:providers': (providers: Record) => void | Promise
}
}
diff --git a/test/cases/core-auth/server/api/test/config.get.ts b/test/cases/core-auth/server/api/test/config.get.ts
index 52ec076..274510a 100644
--- a/test/cases/core-auth/server/api/test/config.get.ts
+++ b/test/cases/core-auth/server/api/test/config.get.ts
@@ -1,9 +1,12 @@
-export default defineEventHandler(() => {
- const runtimeConfig = useRuntimeConfig()
- const auth = runtimeConfig.public.auth as { useDatabase?: boolean, databaseProvider?: string } | undefined
+import { getDatabaseProvider, getDatabaseSource, serverAuth } from '../../../../../../src/runtime/server/utils/auth'
+
+export default defineEventHandler((event) => {
+ serverAuth(event)
+ const databaseProvider = getDatabaseProvider()
return {
- useDatabase: auth?.useDatabase ?? false,
- databaseProvider: auth?.databaseProvider ?? 'none',
+ useDatabase: databaseProvider !== 'none',
+ databaseProvider,
+ databaseSource: getDatabaseSource(),
}
})
diff --git a/test/cases/database-less/server/api/test/config.get.ts b/test/cases/database-less/server/api/test/config.get.ts
index 52ec076..274510a 100644
--- a/test/cases/database-less/server/api/test/config.get.ts
+++ b/test/cases/database-less/server/api/test/config.get.ts
@@ -1,9 +1,12 @@
-export default defineEventHandler(() => {
- const runtimeConfig = useRuntimeConfig()
- const auth = runtimeConfig.public.auth as { useDatabase?: boolean, databaseProvider?: string } | undefined
+import { getDatabaseProvider, getDatabaseSource, serverAuth } from '../../../../../../src/runtime/server/utils/auth'
+
+export default defineEventHandler((event) => {
+ serverAuth(event)
+ const databaseProvider = getDatabaseProvider()
return {
- useDatabase: auth?.useDatabase ?? false,
- databaseProvider: auth?.databaseProvider ?? 'none',
+ useDatabase: databaseProvider !== 'none',
+ databaseProvider,
+ databaseSource: getDatabaseSource(),
}
})
diff --git a/test/cases/manual-db-no-module/.nuxtrc b/test/cases/manual-db-no-module/.nuxtrc
new file mode 100644
index 0000000..749fbc6
--- /dev/null
+++ b/test/cases/manual-db-no-module/.nuxtrc
@@ -0,0 +1 @@
+setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
diff --git a/test/cases/manual-db-no-module/app/app.vue b/test/cases/manual-db-no-module/app/app.vue
new file mode 100644
index 0000000..8f62b8b
--- /dev/null
+++ b/test/cases/manual-db-no-module/app/app.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/test/cases/manual-db-no-module/app/auth.config.ts b/test/cases/manual-db-no-module/app/auth.config.ts
new file mode 100644
index 0000000..ce985ee
--- /dev/null
+++ b/test/cases/manual-db-no-module/app/auth.config.ts
@@ -0,0 +1,3 @@
+import { defineClientAuth } from '../../../../src/runtime/config'
+
+export default defineClientAuth({})
diff --git a/test/cases/manual-db-no-module/nuxt.config.ts b/test/cases/manual-db-no-module/nuxt.config.ts
new file mode 100644
index 0000000..e8fb009
--- /dev/null
+++ b/test/cases/manual-db-no-module/nuxt.config.ts
@@ -0,0 +1,10 @@
+export default defineNuxtConfig({
+ modules: ['../../../src/module'],
+ runtimeConfig: {
+ betterAuthSecret: 'test-secret-for-testing-only-32chars!',
+ public: { siteUrl: 'http://localhost:3000' },
+ },
+ auth: {
+ redirects: { login: '/login', guest: '/' },
+ },
+})
diff --git a/test/cases/manual-db-no-module/server/api/test/config.get.ts b/test/cases/manual-db-no-module/server/api/test/config.get.ts
new file mode 100644
index 0000000..274510a
--- /dev/null
+++ b/test/cases/manual-db-no-module/server/api/test/config.get.ts
@@ -0,0 +1,12 @@
+import { getDatabaseProvider, getDatabaseSource, serverAuth } from '../../../../../../src/runtime/server/utils/auth'
+
+export default defineEventHandler((event) => {
+ serverAuth(event)
+ const databaseProvider = getDatabaseProvider()
+
+ return {
+ useDatabase: databaseProvider !== 'none',
+ databaseProvider,
+ databaseSource: getDatabaseSource(),
+ }
+})
diff --git a/test/cases/manual-db-no-module/server/auth.config.ts b/test/cases/manual-db-no-module/server/auth.config.ts
new file mode 100644
index 0000000..d2dbed4
--- /dev/null
+++ b/test/cases/manual-db-no-module/server/auth.config.ts
@@ -0,0 +1,18 @@
+import { memoryAdapter } from 'better-auth/adapters/memory'
+import { defineServerAuth } from '../../../../src/runtime/config'
+
+const memoryDb = {
+ user: [],
+ session: [],
+ account: [],
+ verification: [],
+ rateLimit: [],
+}
+
+export default defineServerAuth({
+ appName: 'Manual DB No Module Provider',
+ database: memoryAdapter(memoryDb),
+ socialProviders: {
+ github: { clientId: 'test', clientSecret: 'test' },
+ },
+})
diff --git a/test/cases/manual-db/.nuxtrc b/test/cases/manual-db/.nuxtrc
new file mode 100644
index 0000000..c85c9d1
--- /dev/null
+++ b/test/cases/manual-db/.nuxtrc
@@ -0,0 +1 @@
+setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
\ No newline at end of file
diff --git a/test/cases/manual-db/app/app.vue b/test/cases/manual-db/app/app.vue
new file mode 100644
index 0000000..8f62b8b
--- /dev/null
+++ b/test/cases/manual-db/app/app.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/test/cases/manual-db/app/auth.config.ts b/test/cases/manual-db/app/auth.config.ts
new file mode 100644
index 0000000..ce985ee
--- /dev/null
+++ b/test/cases/manual-db/app/auth.config.ts
@@ -0,0 +1,3 @@
+import { defineClientAuth } from '../../../../src/runtime/config'
+
+export default defineClientAuth({})
diff --git a/test/cases/manual-db/nuxt.config.ts b/test/cases/manual-db/nuxt.config.ts
new file mode 100644
index 0000000..07fc56b
--- /dev/null
+++ b/test/cases/manual-db/nuxt.config.ts
@@ -0,0 +1,11 @@
+export default defineNuxtConfig({
+ modules: ['@nuxthub/core', '../../../src/module'],
+ hub: { db: 'sqlite' },
+ runtimeConfig: {
+ betterAuthSecret: 'test-secret-for-testing-only-32chars!',
+ public: { siteUrl: 'http://localhost:3000' },
+ },
+ auth: {
+ redirects: { login: '/login', guest: '/' },
+ },
+})
diff --git a/test/cases/manual-db/server/api/test/config.get.ts b/test/cases/manual-db/server/api/test/config.get.ts
new file mode 100644
index 0000000..274510a
--- /dev/null
+++ b/test/cases/manual-db/server/api/test/config.get.ts
@@ -0,0 +1,12 @@
+import { getDatabaseProvider, getDatabaseSource, serverAuth } from '../../../../../../src/runtime/server/utils/auth'
+
+export default defineEventHandler((event) => {
+ serverAuth(event)
+ const databaseProvider = getDatabaseProvider()
+
+ return {
+ useDatabase: databaseProvider !== 'none',
+ databaseProvider,
+ databaseSource: getDatabaseSource(),
+ }
+})
diff --git a/test/cases/manual-db/server/auth.config.ts b/test/cases/manual-db/server/auth.config.ts
new file mode 100644
index 0000000..b9f8b32
--- /dev/null
+++ b/test/cases/manual-db/server/auth.config.ts
@@ -0,0 +1,18 @@
+import { memoryAdapter } from 'better-auth/adapters/memory'
+import { defineServerAuth } from '../../../../src/runtime/config'
+
+const memoryDb = {
+ user: [],
+ session: [],
+ account: [],
+ verification: [],
+ rateLimit: [],
+}
+
+export default defineServerAuth({
+ appName: 'Manual DB Precedence App',
+ database: memoryAdapter(memoryDb),
+ socialProviders: {
+ github: { clientId: 'test', clientSecret: 'test' },
+ },
+})
diff --git a/test/cases/plugins-type-inference/server/auth.config.ts b/test/cases/plugins-type-inference/server/auth.config.ts
index c55110d..5afc11f 100644
--- a/test/cases/plugins-type-inference/server/auth.config.ts
+++ b/test/cases/plugins-type-inference/server/auth.config.ts
@@ -25,7 +25,7 @@ function customAdminLikePlugin() {
export default defineServerAuth({
emailAndPassword: { enabled: true },
- plugins: [customAdminLikePlugin()],
+ plugins: [customAdminLikePlugin()] as const,
user: {
additionalFields: {
internalCode: {
diff --git a/test/database-provider.test.ts b/test/database-provider.test.ts
index e64f905..9b17a47 100644
--- a/test/database-provider.test.ts
+++ b/test/database-provider.test.ts
@@ -1,92 +1,89 @@
+import type { BetterAuthDatabaseProviderDefinition, BetterAuthDatabaseProviderEnabledContext } from '../src/types/hooks'
import { describe, expect, it } from 'vitest'
import { resolveDatabaseProvider } from '../src/database-provider'
+function createContext(input: Partial = {}): BetterAuthDatabaseProviderEnabledContext {
+ return {
+ nuxt: {} as BetterAuthDatabaseProviderEnabledContext['nuxt'],
+ options: {},
+ clientOnly: false,
+ hasHubDbAvailable: false,
+ ...input,
+ }
+}
+
+function createProvider(partial: Partial = {}): BetterAuthDatabaseProviderDefinition {
+ return {
+ buildDatabaseCode: () => 'export function createDatabase() { return undefined }',
+ ...partial,
+ }
+}
+
describe('resolveDatabaseProvider', () => {
- it('returns nuxthub when explicitly selected and available', () => {
- expect(resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: true,
- hasConvexModule: false,
- convexUrl: '',
- selectedProvider: 'nuxthub',
- })).toEqual({ provider: 'nuxthub', convexUrl: '' })
- })
+ it('selects nuxthub when hub db is available', () => {
+ const providers = {
+ none: createProvider({ priority: 0 }),
+ nuxthub: createProvider({
+ priority: 100,
+ isEnabled: ({ hasHubDbAvailable }) => hasHubDbAvailable,
+ }),
+ }
- it('throws when nuxthub is explicitly selected but hub db is unavailable', () => {
- expect(() => resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: false,
- hasConvexModule: false,
- convexUrl: '',
- selectedProvider: 'nuxthub',
- })).toThrow('@nuxthub/core with hub.db is not configured')
- })
+ const resolved = resolveDatabaseProvider({
+ providers,
+ context: createContext({ hasHubDbAvailable: true }),
+ })
- it('returns convex when explicitly selected with valid context', () => {
- expect(resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: false,
- hasConvexModule: true,
- convexUrl: 'https://example.convex.cloud',
- selectedProvider: 'convex',
- })).toEqual({ provider: 'convex', convexUrl: 'https://example.convex.cloud' })
+ expect(resolved.id).toBe('nuxthub')
})
- it('throws when convex is selected in clientOnly mode', () => {
- expect(() => resolveDatabaseProvider({
- clientOnly: true,
- hasHubDb: false,
- hasConvexModule: true,
- convexUrl: 'https://example.convex.cloud',
- selectedProvider: 'convex',
- })).toThrow('"convex" is not available in clientOnly mode')
- })
+ it('falls back to none when hub db is unavailable', () => {
+ const providers = {
+ none: createProvider({ priority: 0 }),
+ nuxthub: createProvider({
+ priority: 100,
+ isEnabled: ({ hasHubDbAvailable }) => hasHubDbAvailable,
+ }),
+ }
- it('throws when convex is selected but module is missing', () => {
- expect(() => resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: false,
- hasConvexModule: false,
- convexUrl: 'https://example.convex.cloud',
- selectedProvider: 'convex',
- })).toThrow('nuxt-convex is not installed')
- })
+ const resolved = resolveDatabaseProvider({
+ providers,
+ context: createContext({ hasHubDbAvailable: false }),
+ })
- it('throws when convex is selected and URL is missing', () => {
- expect(() => resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: false,
- hasConvexModule: true,
- convexUrl: '',
- selectedProvider: 'convex',
- })).toThrow('no Convex URL was found')
+ expect(resolved.id).toBe('none')
})
- it('returns none when explicitly selected', () => {
- expect(resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: true,
- hasConvexModule: true,
- convexUrl: 'https://example.convex.cloud',
- selectedProvider: 'none',
- })).toEqual({ provider: 'none', convexUrl: '' })
- })
+ it('prefers a higher-priority external provider when enabled', () => {
+ const providers = {
+ none: createProvider({ priority: 0 }),
+ nuxthub: createProvider({
+ priority: 100,
+ isEnabled: ({ hasHubDbAvailable }) => hasHubDbAvailable,
+ }),
+ external: createProvider({
+ priority: 200,
+ isEnabled: () => true,
+ }),
+ }
- it('auto-selects nuxthub when hub db is available', () => {
- expect(resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: true,
- hasConvexModule: false,
- convexUrl: '',
- })).toEqual({ provider: 'nuxthub', convexUrl: '' })
+ const resolved = resolveDatabaseProvider({
+ providers,
+ context: createContext({ hasHubDbAvailable: true }),
+ })
+
+ expect(resolved.id).toBe('external')
})
- it('auto-selects none when hub db is unavailable', () => {
- expect(resolveDatabaseProvider({
- clientOnly: false,
- hasHubDb: false,
- hasConvexModule: true,
- convexUrl: 'https://example.convex.cloud',
- })).toEqual({ provider: 'none', convexUrl: '' })
+ it('throws when no provider is enabled', () => {
+ const providers = {
+ nuxthub: createProvider({ isEnabled: () => false }),
+ external: createProvider({ isEnabled: () => false }),
+ }
+
+ expect(() => resolveDatabaseProvider({
+ providers,
+ context: createContext(),
+ })).toThrow('No database provider is enabled')
})
})
diff --git a/test/devtools-database-eligibility.test.ts b/test/devtools-database-eligibility.test.ts
new file mode 100644
index 0000000..80b41bf
--- /dev/null
+++ b/test/devtools-database-eligibility.test.ts
@@ -0,0 +1,48 @@
+import { describe, expect, it } from 'vitest'
+import { isDevtoolsDatabaseEligible } from '../src/runtime/utils/devtools-database'
+
+describe('isDevtoolsDatabaseEligible', () => {
+ it('returns true for nuxthub + module', () => {
+ expect(isDevtoolsDatabaseEligible({
+ databaseProvider: 'nuxthub',
+ databaseSource: 'module',
+ })).toBe(true)
+ })
+
+ it('returns false for nuxthub + user', () => {
+ expect(isDevtoolsDatabaseEligible({
+ databaseProvider: 'nuxthub',
+ databaseSource: 'user',
+ })).toBe(false)
+ })
+
+ it('returns false for custom provider + module', () => {
+ expect(isDevtoolsDatabaseEligible({
+ databaseProvider: 'custom-db',
+ databaseSource: 'module',
+ })).toBe(false)
+ })
+
+ it('returns false for none provider', () => {
+ expect(isDevtoolsDatabaseEligible({
+ databaseProvider: 'none',
+ databaseSource: 'module',
+ })).toBe(false)
+ })
+
+ it('returns true from fallback nuxthub provider when config is unavailable', () => {
+ expect(isDevtoolsDatabaseEligible({
+ fallbackModuleProvider: 'nuxthub',
+ })).toBe(true)
+ })
+
+ it('returns false from fallback none/custom provider', () => {
+ expect(isDevtoolsDatabaseEligible({
+ fallbackModuleProvider: 'none',
+ })).toBe(false)
+
+ expect(isDevtoolsDatabaseEligible({
+ fallbackModuleProvider: 'custom-db',
+ })).toBe(false)
+ })
+})
diff --git a/test/exports/module.yaml b/test/exports/module.yaml
index 48972f2..bab0f66 100644
--- a/test/exports/module.yaml
+++ b/test/exports/module.yaml
@@ -5,5 +5,3 @@
./config:
defineClientAuth: function
defineServerAuth: function
-./adapters/convex:
- createConvexHttpAdapter: function
diff --git a/test/fixtures/convex-url-option-removed/.nuxtrc b/test/fixtures/convex-url-option-removed/.nuxtrc
new file mode 100644
index 0000000..749fbc6
--- /dev/null
+++ b/test/fixtures/convex-url-option-removed/.nuxtrc
@@ -0,0 +1 @@
+setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
diff --git a/test/fixtures/convex-url-option-removed/app/auth.config.ts b/test/fixtures/convex-url-option-removed/app/auth.config.ts
new file mode 100644
index 0000000..ce985ee
--- /dev/null
+++ b/test/fixtures/convex-url-option-removed/app/auth.config.ts
@@ -0,0 +1,3 @@
+import { defineClientAuth } from '../../../../src/runtime/config'
+
+export default defineClientAuth({})
diff --git a/test/fixtures/convex-url-option-removed/nuxt.config.ts b/test/fixtures/convex-url-option-removed/nuxt.config.ts
new file mode 100644
index 0000000..dbb4670
--- /dev/null
+++ b/test/fixtures/convex-url-option-removed/nuxt.config.ts
@@ -0,0 +1,12 @@
+export default defineNuxtConfig({
+ modules: ['../../../src/module'],
+ runtimeConfig: {
+ betterAuthSecret: 'test-secret-for-testing-only-32chars!',
+ public: { siteUrl: 'http://localhost:3000' },
+ },
+ auth: {
+ database: {
+ convexUrl: 'https://example.convex.cloud',
+ },
+ },
+})
diff --git a/test/fixtures/convex-url-option-removed/server/auth.config.ts b/test/fixtures/convex-url-option-removed/server/auth.config.ts
new file mode 100644
index 0000000..dcafab6
--- /dev/null
+++ b/test/fixtures/convex-url-option-removed/server/auth.config.ts
@@ -0,0 +1,8 @@
+import { defineServerAuth } from '../../../../src/runtime/config'
+
+export default defineServerAuth({
+ appName: 'Removed Convex URL Option',
+ socialProviders: {
+ github: { clientId: 'test', clientSecret: 'test' },
+ },
+})
diff --git a/test/fixtures/mixed-database-options-removed/.nuxtrc b/test/fixtures/mixed-database-options-removed/.nuxtrc
new file mode 100644
index 0000000..749fbc6
--- /dev/null
+++ b/test/fixtures/mixed-database-options-removed/.nuxtrc
@@ -0,0 +1 @@
+setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
diff --git a/test/fixtures/mixed-database-options-removed/app/auth.config.ts b/test/fixtures/mixed-database-options-removed/app/auth.config.ts
new file mode 100644
index 0000000..ce985ee
--- /dev/null
+++ b/test/fixtures/mixed-database-options-removed/app/auth.config.ts
@@ -0,0 +1,3 @@
+import { defineClientAuth } from '../../../../src/runtime/config'
+
+export default defineClientAuth({})
diff --git a/test/fixtures/mixed-database-options-removed/nuxt.config.ts b/test/fixtures/mixed-database-options-removed/nuxt.config.ts
new file mode 100644
index 0000000..6970149
--- /dev/null
+++ b/test/fixtures/mixed-database-options-removed/nuxt.config.ts
@@ -0,0 +1,13 @@
+export default defineNuxtConfig({
+ modules: ['../../../src/module'],
+ runtimeConfig: {
+ betterAuthSecret: 'test-secret-for-testing-only-32chars!',
+ public: { siteUrl: 'http://localhost:3000' },
+ },
+ auth: {
+ database: {
+ provider: 'nuxthub',
+ convexUrl: 'https://example.convex.cloud',
+ },
+ },
+})
diff --git a/test/fixtures/mixed-database-options-removed/server/auth.config.ts b/test/fixtures/mixed-database-options-removed/server/auth.config.ts
new file mode 100644
index 0000000..33ab282
--- /dev/null
+++ b/test/fixtures/mixed-database-options-removed/server/auth.config.ts
@@ -0,0 +1,8 @@
+import { defineServerAuth } from '../../../../src/runtime/config'
+
+export default defineServerAuth({
+ appName: 'Removed Mixed Database Options',
+ socialProviders: {
+ github: { clientId: 'test', clientSecret: 'test' },
+ },
+})
diff --git a/test/fixtures/provider-option-removed/.nuxtrc b/test/fixtures/provider-option-removed/.nuxtrc
new file mode 100644
index 0000000..c85c9d1
--- /dev/null
+++ b/test/fixtures/provider-option-removed/.nuxtrc
@@ -0,0 +1 @@
+setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
\ No newline at end of file
diff --git a/test/fixtures/provider-option-removed/app/auth.config.ts b/test/fixtures/provider-option-removed/app/auth.config.ts
new file mode 100644
index 0000000..ce985ee
--- /dev/null
+++ b/test/fixtures/provider-option-removed/app/auth.config.ts
@@ -0,0 +1,3 @@
+import { defineClientAuth } from '../../../../src/runtime/config'
+
+export default defineClientAuth({})
diff --git a/test/fixtures/provider-option-removed/nuxt.config.ts b/test/fixtures/provider-option-removed/nuxt.config.ts
new file mode 100644
index 0000000..0c4444a
--- /dev/null
+++ b/test/fixtures/provider-option-removed/nuxt.config.ts
@@ -0,0 +1,12 @@
+export default defineNuxtConfig({
+ modules: ['../../../src/module'],
+ runtimeConfig: {
+ betterAuthSecret: 'test-secret-for-testing-only-32chars!',
+ public: { siteUrl: 'http://localhost:3000' },
+ },
+ auth: {
+ database: {
+ provider: 'nuxthub',
+ },
+ },
+})
diff --git a/test/fixtures/provider-option-removed/server/auth.config.ts b/test/fixtures/provider-option-removed/server/auth.config.ts
new file mode 100644
index 0000000..7dc83f7
--- /dev/null
+++ b/test/fixtures/provider-option-removed/server/auth.config.ts
@@ -0,0 +1,8 @@
+import { defineServerAuth } from '../../../../src/runtime/config'
+
+export default defineServerAuth({
+ appName: 'Removed Provider Option',
+ socialProviders: {
+ github: { clientId: 'test', clientSecret: 'test' },
+ },
+})
diff --git a/test/manual-db-no-module-provider.test.ts b/test/manual-db-no-module-provider.test.ts
new file mode 100644
index 0000000..3961a33
--- /dev/null
+++ b/test/manual-db-no-module-provider.test.ts
@@ -0,0 +1,16 @@
+import { fileURLToPath } from 'node:url'
+import { $fetch, setup } from '@nuxt/test-utils/e2e'
+import { describe, expect, it } from 'vitest'
+
+describe('manual Better Auth database without module provider', async () => {
+ await setup({
+ rootDir: fileURLToPath(new URL('./cases/manual-db-no-module', import.meta.url)),
+ })
+
+ it('reports effective user database metadata', async () => {
+ const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string, databaseSource: 'module' | 'user' }
+ expect(response.useDatabase).toBe(true)
+ expect(response.databaseProvider).toBe('user')
+ expect(response.databaseSource).toBe('user')
+ })
+})
diff --git a/test/manual-db-precedence.test.ts b/test/manual-db-precedence.test.ts
new file mode 100644
index 0000000..8139f84
--- /dev/null
+++ b/test/manual-db-precedence.test.ts
@@ -0,0 +1,16 @@
+import { fileURLToPath } from 'node:url'
+import { $fetch, setup } from '@nuxt/test-utils/e2e'
+import { describe, expect, it } from 'vitest'
+
+describe('manual Better Auth database precedence', async () => {
+ await setup({
+ rootDir: fileURLToPath(new URL('./cases/manual-db', import.meta.url)),
+ })
+
+ it('uses user-configured database over module auto provider', async () => {
+ const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string, databaseSource: 'module' | 'user' }
+ expect(response.useDatabase).toBe(true)
+ expect(response.databaseProvider).toBe('user')
+ expect(response.databaseSource).toBe('user')
+ })
+})
diff --git a/test/module.test.ts b/test/module.test.ts
index 52ed6f8..4f95fb5 100644
--- a/test/module.test.ts
+++ b/test/module.test.ts
@@ -20,9 +20,10 @@ describe('nuxt-better-auth module', async () => {
})
it('exposes runtime database metadata as nuxthub', async () => {
- const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string }
+ const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string, databaseSource: 'module' | 'user' }
expect(response.useDatabase).toBe(true)
expect(response.databaseProvider).toBe('nuxthub')
+ expect(response.databaseSource).toBe('module')
})
})
diff --git a/test/no-db.test.ts b/test/no-db.test.ts
index b65774d..4698358 100644
--- a/test/no-db.test.ts
+++ b/test/no-db.test.ts
@@ -18,8 +18,9 @@ describe('no-db mode (NuxtHub without database)', async () => {
})
it('exposes runtime database metadata as none', async () => {
- const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string }
+ const response = await $fetch('/api/test/config') as { useDatabase: boolean, databaseProvider: string, databaseSource: 'module' | 'user' }
expect(response.useDatabase).toBe(false)
expect(response.databaseProvider).toBe('none')
+ expect(response.databaseSource).toBe('module')
})
})
diff --git a/test/provider-option-removed.test.ts b/test/provider-option-removed.test.ts
new file mode 100644
index 0000000..fdee931
--- /dev/null
+++ b/test/provider-option-removed.test.ts
@@ -0,0 +1,49 @@
+import { fileURLToPath } from 'node:url'
+import { x } from 'tinyexec'
+import { describe, expect, it } from 'vitest'
+
+const cases = [
+ {
+ name: 'provider',
+ fixture: './fixtures/provider-option-removed',
+ expectedKeys: ['auth.database.provider'],
+ expectedRemoveList: 'Remove: auth.database.provider',
+ },
+ {
+ name: 'convexUrl',
+ fixture: './fixtures/convex-url-option-removed',
+ expectedKeys: ['auth.database.convexUrl'],
+ expectedRemoveList: 'Remove: auth.database.convexUrl',
+ },
+ {
+ name: 'mixed database options',
+ fixture: './fixtures/mixed-database-options-removed',
+ expectedKeys: ['auth.database.convexUrl', 'auth.database.provider'],
+ expectedRemoveList: 'Remove: auth.database.convexUrl, auth.database.provider',
+ },
+] as const
+
+describe('removed auth.database.* options', () => {
+ it.each(cases)('throws a migration error when $name is configured', async ({ fixture, expectedKeys, expectedRemoveList }) => {
+ const fixtureDir = fileURLToPath(new URL(fixture, import.meta.url))
+ const nuxiBin = fileURLToPath(new URL('../node_modules/.bin/nuxi', import.meta.url))
+
+ const res = await x(nuxiBin, ['prepare'], {
+ nodeOptions: {
+ cwd: fixtureDir,
+ env: {
+ ...process.env,
+ CI: '1',
+ },
+ },
+ throwOnError: false,
+ })
+
+ expect(res.exitCode).not.toBe(0)
+ const output = `${res.stdout}\n${res.stderr}`
+ expect(output).toContain('auth.database options have been removed')
+ expect(output).toContain(expectedRemoveList)
+ for (const key of expectedKeys)
+ expect(output).toContain(key)
+ }, 60_000)
+})
diff --git a/test/schema-generator.test.ts b/test/schema-generator.test.ts
index bc6e456..e8788cf 100644
--- a/test/schema-generator.test.ts
+++ b/test/schema-generator.test.ts
@@ -1,10 +1,9 @@
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { getAuthTables } from 'better-auth/db'
-import { twoFactor } from 'better-auth/plugins'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { defineClientAuth, defineServerAuth } from '../src/runtime/config'
-import { generateConvexSchema, generateDrizzleSchema, loadUserAuthConfig } from '../src/schema-generator'
+import { generateDrizzleSchema, loadUserAuthConfig } from '../src/schema-generator'
const TEST_DIR = join(import.meta.dirname, '.test-configs')
@@ -93,17 +92,6 @@ describe('getAuthTables with secondaryStorage', () => {
})
})
-describe('generateConvexSchema', () => {
- it('includes plugin tables when plugin config is merged', async () => {
- const userConfig = {}
- const extendedPlugins = [twoFactor()]
- const authOptions = { ...userConfig, plugins: extendedPlugins }
- const schema = await generateConvexSchema(authOptions)
-
- expect(schema).toContain('twoFactor: defineTable')
- })
-})
-
describe('loadUserAuthConfig', () => {
it('returns empty object for non-existent file (dev mode)', async () => {
const result = await loadUserAuthConfig(join(TEST_DIR, 'nonexistent.ts'), false)
@@ -153,21 +141,21 @@ describe('defineServerAuth', () => {
it('accepts object syntax and returns config factory', () => {
const factory = defineServerAuth({ appName: 'Test', emailAndPassword: { enabled: true } })
expect(typeof factory).toBe('function')
- const config = factory({ runtimeConfig: {} as any, db: null })
+ const config = factory({ runtimeConfig: {} as any, db: undefined })
expect(config).toEqual({ appName: 'Test', emailAndPassword: { enabled: true } })
})
it('accepts function syntax and returns config factory', () => {
const factory = defineServerAuth(ctx => ({ appName: 'Dynamic', runtimeBased: !!ctx.runtimeConfig }))
expect(typeof factory).toBe('function')
- const config = factory({ runtimeConfig: { public: {} } as any, db: null })
+ const config = factory({ runtimeConfig: { public: {} } as any, db: undefined })
expect(config).toEqual({ appName: 'Dynamic', runtimeBased: true })
})
it('function syntax receives context', () => {
- const factory = defineServerAuth(({ db }) => ({ hasDb: db !== null }))
+ const factory = defineServerAuth(({ db }) => ({ hasDb: db !== undefined }))
expect(factory({ runtimeConfig: {} as any, db: {} as any })).toEqual({ hasDb: true })
- expect(factory({ runtimeConfig: {} as any, db: null })).toEqual({ hasDb: false })
+ expect(factory({ runtimeConfig: {} as any, db: undefined })).toEqual({ hasDb: false })
})
})
diff --git a/test/use-user-session.test.ts b/test/use-user-session.test.ts
index d74cce2..b9ec3c2 100644
--- a/test/use-user-session.test.ts
+++ b/test/use-user-session.test.ts
@@ -36,7 +36,7 @@ const sessionAtom = ref({
error: null,
})
-const mockClient = {
+const mockClient: Record = {
useSession: vi.fn(() => sessionAtom),
getSession: vi.fn(async () => ({ data: null })),
$store: {
@@ -108,6 +108,7 @@ describe('useUserSession hydration bootstrap', () => {
mockClient.getSession.mockClear()
mockClient.$store.listen.mockClear()
mockClient.signOut.mockClear()
+ mockClient.updateUser = undefined
mockClient.signIn.social.mockClear()
mockClient.signIn.email.mockClear()
mockClient.signUp.email.mockClear()
@@ -203,6 +204,50 @@ describe('useUserSession hydration bootstrap', () => {
expect(auth.user.value).toEqual({ id: 'user-2', email: 'user@example.com' })
})
+ it('updateUser persists on client and updates local state optimistically', async () => {
+ mockClient.updateUser = vi.fn(async () => ({ data: { status: true } }))
+ const useUserSession = await loadUseUserSession()
+ const auth = useUserSession()
+ auth.user.value = { id: 'user-1', name: 'Old', email: 'a@b.com' }
+
+ await auth.updateUser({ name: 'New' })
+
+ expect(mockClient.updateUser).toHaveBeenCalledWith({ name: 'New' })
+ expect(auth.user.value!.name).toBe('New')
+ })
+
+ it('updateUser reverts local state when the server call throws', async () => {
+ mockClient.updateUser = vi.fn(async () => {
+ throw new Error('fail')
+ })
+ const useUserSession = await loadUseUserSession()
+ const auth = useUserSession()
+ auth.user.value = { id: 'user-1', name: 'Old', email: 'a@b.com' }
+
+ await expect(auth.updateUser({ name: 'New' })).rejects.toThrow('fail')
+ expect(auth.user.value!.name).toBe('Old')
+ })
+
+ it('updateUser reverts local state when server returns an error payload', async () => {
+ mockClient.updateUser = vi.fn(async () => ({ error: { message: 'invalid user update' } }))
+ const useUserSession = await loadUseUserSession()
+ const auth = useUserSession()
+ auth.user.value = { id: 'user-1', name: 'Old', email: 'a@b.com' }
+
+ await expect(auth.updateUser({ name: 'New' })).rejects.toThrow('invalid user update')
+ expect(auth.user.value!.name).toBe('Old')
+ })
+
+ it('updateUser only updates local state on server (no client)', async () => {
+ setRuntimeFlags({ client: false, server: true })
+ const useUserSession = await loadUseUserSession()
+ const auth = useUserSession()
+ auth.user.value = { id: 'user-1', name: 'Old', email: 'a@b.com' }
+
+ await auth.updateUser({ name: 'New' })
+ expect(auth.user.value!.name).toBe('New')
+ })
+
it('syncs session on $sessionSignal when option is enabled and SSR payload is hydrated', async () => {
payload.serverRendered = true
runtimeConfig.public.auth.session.skipHydratedSsrGetSession = true