Skip to content

Commit 91505f8

Browse files
authored
fix(auth): rebuild nuxthub adapters and server auth type refs
* fix(auth): rebuild nuxthub postgres adapters per request * fix(ci): restore plugin inference fixture * fix(auth): use nuxt imports in server runtime * fix(ci): align auth runtime typing stubs * chore(pr): keep hyperdrive fix focused * chore(pr): keep auth runtime import unchanged * fix(auth): resolve server auth aliases for typecheck * fix(auth): keep server auth types out of shared refs * test: cover server auth db context typing * fix(types): augment server auth config context * fix(types): restore auth config inference in project builds * test: satisfy db context lint rule
1 parent 364bd3d commit 91505f8

10 files changed

Lines changed: 195 additions & 60 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"lint": "eslint .",
5454
"lint:fix": "eslint . --fix",
5555
"typecheck": "vue-tsc --noEmit",
56-
"typecheck:runtime-server": "tsc --noEmit --pretty false -p src/runtime/server/tsconfig.json",
56+
"typecheck:runtime-server": "cd test/cases/plugins-type-inference && nuxi prepare && vue-tsc --noEmit --pretty false -p .nuxt/tsconfig.server.json",
5757
"typecheck:playground": "pnpm -C playground exec nuxi prepare && pnpm -C playground exec vue-tsc --noEmit -p .nuxt/tsconfig.app.json",
5858
"test": "vitest run",
5959
"test:watch": "vitest watch"

src/module.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Nuxt } from '@nuxt/schema'
22
import type { BetterAuthModuleOptions } from './runtime/config'
33
import type { BetterAuthDatabaseProviderSetupContext } from './types/hooks'
4+
import { existsSync, readFileSync } from 'node:fs'
45
import { mkdir, writeFile } from 'node:fs/promises'
56
import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
67
import { consola as _consola } from 'consola'
@@ -18,6 +19,33 @@ import './types/hooks'
1819

1920
const consola = _consola.withTag('nuxt-better-auth')
2021

22+
function isServerConfigSharedTypeSafe(serverConfigPath: string): boolean {
23+
const resolvedPath = [
24+
serverConfigPath,
25+
`${serverConfigPath}.ts`,
26+
`${serverConfigPath}.mts`,
27+
`${serverConfigPath}.cts`,
28+
`${serverConfigPath}.js`,
29+
`${serverConfigPath}.mjs`,
30+
`${serverConfigPath}.cjs`,
31+
].find(path => existsSync(path))
32+
33+
if (!resolvedPath)
34+
return false
35+
36+
const contents = readFileSync(resolvedPath, 'utf8')
37+
38+
return !(
39+
/from\s+['"]#server/.test(contents)
40+
|| /from\s+['"]#layers\//.test(contents)
41+
|| /from\s+['"]~~/.test(contents)
42+
|| /from\s+['"]@@/.test(contents)
43+
|| /\bdb\b/.test(contents)
44+
|| /\bsessionHookAfter\b/.test(contents)
45+
|| /@nuxthub\/db/.test(contents)
46+
)
47+
}
48+
2149
async function createDefaultAuthConfigFiles(nuxt: Nuxt): Promise<void> {
2250
const configs = resolveAuthConfigDescriptors(nuxt)
2351

@@ -136,6 +164,7 @@ export default defineNuxtModule<BetterAuthModuleOptions>({
136164
serverConfigPath: setup.serverTypes.serverConfigPath,
137165
hasHubDb: setup.serverTypes.hasHubDb,
138166
runtimeTypesPath: resolver.resolve('./runtime/types'),
167+
sharedServerConfigSafe: isServerConfigSharedTypeSafe(setup.serverTypes.serverConfigPath),
139168
})
140169
}
141170

src/module/hooks.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Nuxt, NuxtPage } from '@nuxt/schema'
22
import type { AuthRouteRules } from '../runtime/types'
3+
import { existsSync, statSync } from 'node:fs'
34
import { addComponentsDir, addImportsDir, addPlugin, addServerHandler, addServerImports, addServerImportsDir, addServerScanDir, extendPages, hasNuxtModule, installModule, updateTemplates } from '@nuxt/kit'
45
import { defu } from 'defu'
5-
import { join } from 'pathe'
6+
import { isAbsolute, join } from 'pathe'
67
import { createRouter, toRouteMatcher } from 'radix3'
78
import { setupDevTools } from '../devtools'
89

@@ -90,6 +91,15 @@ export function registerPrepareTypesHook(input: RegisterPrepareTypesHookInput):
9091
nodeTsConfig.compilerOptions.paths[key] = [value]
9192
}
9293

94+
for (const [key, value] of Object.entries(nuxt.options.alias)) {
95+
if (typeof value !== 'string' || !isAbsolute(value))
96+
continue
97+
98+
nodeTsConfig.compilerOptions.paths[key] ||= [value]
99+
if (!key.includes('*') && existsSync(value) && statSync(value).isDirectory())
100+
nodeTsConfig.compilerOptions.paths[`${key}/*`] ||= [join(value, '*')]
101+
}
102+
93103
nodeTsConfig.compilerOptions.paths['#server/*'] = [join(serverDir, '*')]
94104

95105
for (const path of projectReferenceTypePaths) {

src/module/templates.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export function createDatabase(event) {
9797
registerClientCleanup(event, client)
9898
return database
9999
}
100-
101100
const client = postgres(hyperdrive.connectionString, {
102101
prepare: false,
103102
onnotice: () => {},

src/module/type-templates.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ interface RegisterServerTypeTemplatesInput {
44
serverConfigPath: string
55
hasHubDb: boolean
66
runtimeTypesPath: string
7+
sharedServerConfigSafe: boolean
78
}
89

910
export function registerServerTypeTemplates(input: RegisterServerTypeTemplatesInput): void {
10-
const { serverConfigPath, hasHubDb, runtimeTypesPath } = input
11+
const { serverConfigPath, hasHubDb, runtimeTypesPath, sharedServerConfigSafe } = input
12+
const serverConfigTypeTemplateOptions = sharedServerConfigSafe
13+
? { nuxt: true, nitro: true, node: true, shared: true }
14+
: { nuxt: true, nitro: true, node: true }
1115

1216
addTypeTemplate({
1317
filename: 'types/auth-secondary-storage.d.ts',
@@ -53,12 +57,40 @@ declare module '#auth/schema' {
5357
`,
5458
}, { nitro: true })
5559

60+
addTypeTemplate({
61+
filename: 'types/nuxt-better-auth-server-context.d.ts',
62+
getContents: () => `
63+
/// <reference path="./nitro-imports.d.ts" />
64+
/// <reference path="./auth-database.d.ts" />
65+
/// <reference path="./auth-schema.d.ts" />
66+
/// <reference path="./auth-secondary-storage.d.ts" />
67+
${hasHubDb ? '/// <reference path="../hub/db.d.ts" />' : ''}
68+
69+
export {}
70+
`,
71+
}, { node: true })
72+
73+
addTypeTemplate({
74+
filename: 'types/nuxt-better-auth-config-context.d.ts',
75+
getContents: () => `
76+
import type { RuntimeConfig } from 'nuxt/schema'
77+
78+
declare module '@onmax/nuxt-better-auth/config' {
79+
interface ServerAuthContextExtension {
80+
runtimeConfig: RuntimeConfig
81+
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : 'undefined'}
82+
requestOrigin?: string
83+
}
84+
}
85+
86+
`,
87+
}, { nuxt: true, nitro: true, node: true, shared: true })
88+
5689
addTypeTemplate({
5790
filename: 'types/nuxt-better-auth-infer.d.ts',
5891
getContents: () => `
5992
import type { BetterAuthOptions, BetterAuthPlugin, InferPluginTypes, UnionToIntersection } from 'better-auth'
6093
import type { InferFieldsOutput } from 'better-auth/db'
61-
import type { RuntimeConfig } from 'nuxt/schema'
6294
import type createServerAuth from '${serverConfigPath}'
6395
6496
type _RawConfig = ReturnType<typeof createServerAuth>
@@ -88,30 +120,10 @@ type _SessionFallback = _InferModelFieldsFromPlugins<_RawPlugins, 'session'> & _
88120
declare module '#nuxt-better-auth' {
89121
interface AuthUser extends _UserFallback {}
90122
interface AuthSession extends _SessionFallback {}
91-
interface ServerAuthContext {
92-
runtimeConfig: RuntimeConfig
93-
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : 'undefined'}
94-
requestOrigin?: string
95-
}
96123
type PluginTypes = InferPluginTypes<_Config>
97124
}
98-
99-
interface _AugmentedServerAuthContext {
100-
runtimeConfig: RuntimeConfig
101-
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : 'undefined'}
102-
requestOrigin?: string
103-
}
104-
105-
declare module '@onmax/nuxt-better-auth/config' {
106-
import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'
107-
type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
108-
plugins?: readonly BetterAuthPlugin[]
109-
}
110-
export function defineServerAuth<const R>(config: (ctx: _AugmentedServerAuthContext) => R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
111-
export function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
112-
}
113125
`,
114-
}, { nuxt: true, nitro: true, node: true, shared: true })
126+
}, serverConfigTypeTemplateOptions)
115127

116128
addTypeTemplate({
117129
filename: 'types/nuxt-better-auth-social-providers.d.ts',
@@ -128,7 +140,7 @@ declare module '#nuxt-better-auth' {
128140
}
129141
}
130142
`,
131-
}, { nuxt: true, nitro: true, node: true, shared: true })
143+
}, serverConfigTypeTemplateOptions)
132144

133145
addTypeTemplate({
134146
filename: 'types/nuxt-better-auth-nitro.d.ts',
@@ -326,7 +338,7 @@ declare module 'nitro/types' {
326338
}
327339
export {}
328340
`,
329-
}, { nuxt: true, nitro: true, node: true })
341+
}, { nitro: true, node: true })
330342
}
331343

332344
interface RegisterSharedTypeTemplatesInput {

src/runtime/config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'
22
import type { BetterAuthClientOptions } from 'better-auth/client'
33
import type { Casing } from 'drizzle-orm/utils'
4-
import type { ServerAuthContext } from './types/augment'
4+
import type { ServerAuthContext as BaseServerAuthContext } from './types/augment'
55
import { createAuthClient } from 'better-auth/vue'
66

7-
// Re-export for declaration merging with generated types
8-
export type { ServerAuthContext }
7+
export interface ServerAuthContextExtension {}
8+
export type ServerAuthContext = BaseServerAuthContext & ServerAuthContextExtension
99

1010
export interface ClientAuthContext {
1111
siteUrl: string
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
22

3-
export default defineServerAuth(() => ({
4-
emailAndPassword: {
5-
enabled: true,
6-
},
7-
databaseHooks: {
8-
session: {
9-
create: {
10-
async after() {
11-
await sessionHookAfter()
3+
export default defineServerAuth(({ db: _db }) => {
4+
type _DbSelect = typeof _db.select
5+
6+
return {
7+
emailAndPassword: {
8+
enabled: true,
9+
},
10+
databaseHooks: {
11+
session: {
12+
create: {
13+
async after() {
14+
await sessionHookAfter()
15+
},
1216
},
1317
},
1418
},
15-
},
16-
}))
19+
}
20+
})
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
{
2-
"extends": "./.nuxt/tsconfig.json",
32
"compilerOptions": {
4-
"noEmit": true
3+
"target": "ESNext",
4+
"lib": [
5+
"ESNext",
6+
"DOM"
7+
],
8+
"baseUrl": ".",
9+
"module": "preserve",
10+
"moduleResolution": "bundler",
11+
"paths": {
12+
"#auth/client": ["./app/auth.config"],
13+
"#auth/server": ["./server/auth.config"],
14+
"#nuxt-better-auth": ["../../../src/runtime/types/augment"]
15+
},
16+
"types": [],
17+
"strict": true,
18+
"noEmit": true,
19+
"skipLibCheck": true
520
},
6-
"include": [
7-
"./.nuxt/nuxt.d.ts",
21+
"files": [
822
"./virtual-modules.d.ts",
23+
"./.nuxt/types/nuxt-better-auth-infer.d.ts",
24+
"./.nuxt/types/nuxt-better-auth-social-providers.d.ts",
25+
"./.nuxt/types/nuxt-better-auth-nitro.d.ts",
926
"./typecheck-target.ts"
1027
]
1128
}
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { sessionHookAfter } from '#server/utils/hooks'
22
import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
33

4-
export default defineServerAuth(() => ({
5-
emailAndPassword: {
6-
enabled: true,
7-
},
8-
databaseHooks: {
9-
session: {
10-
create: {
11-
async after() {
12-
await sessionHookAfter()
4+
export default defineServerAuth(({ db: _db }) => {
5+
type _DbSelect = typeof _db.select
6+
7+
return {
8+
emailAndPassword: {
9+
enabled: true,
10+
},
11+
databaseHooks: {
12+
session: {
13+
create: {
14+
async after() {
15+
await sessionHookAfter()
16+
},
1317
},
1418
},
1519
},
16-
},
17-
}))
20+
}
21+
})

test/server-auth-project-references-typecheck.test.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { spawnSync } from 'node:child_process'
2-
import { existsSync } from 'node:fs'
2+
import { existsSync, readFileSync } from 'node:fs'
33
import { fileURLToPath } from 'node:url'
44
import { beforeAll, describe, expect, it } from 'vitest'
55

@@ -39,12 +39,72 @@ function runProjectReferenceTypecheck(fixtureDir: string) {
3939
expect(typecheck.status, `vue-tsc -b failed:\n${typecheck.stdout}\n${typecheck.stderr}`).toBe(0)
4040
}
4141

42+
function readGeneratedJson(fixtureDir: string, filePath: string) {
43+
return JSON.parse(readFileSync(`${fixtureDir}/.nuxt/${filePath}`, 'utf8'))
44+
}
45+
46+
function generatedReferences(fixtureDir: string, filePath: string) {
47+
return (readGeneratedJson(fixtureDir, filePath).references || [])
48+
.map((reference: { path?: string }) => reference.path)
49+
.filter(Boolean)
50+
}
51+
52+
function expectSharedTypeReferencesToStayClientSafe(fixtureDir: string) {
53+
const sharedReferences = generatedReferences(fixtureDir, 'tsconfig.shared.json')
54+
const serverOnlyReferences = [
55+
'types/nuxt-better-auth-server-context.d.ts',
56+
'types/nuxt-better-auth-infer.d.ts',
57+
'types/nuxt-better-auth-social-providers.d.ts',
58+
'types/nuxt-better-auth-nitro.d.ts',
59+
'types/nitro-imports.d.ts',
60+
'types/auth-database.d.ts',
61+
'types/auth-schema.d.ts',
62+
'types/auth-secondary-storage.d.ts',
63+
'hub/db.d.ts',
64+
]
65+
66+
for (const reference of serverOnlyReferences)
67+
expect(sharedReferences).not.toContain(reference)
68+
}
69+
70+
function expectServerContextToAvoidNuxthubAugmentation(fixtureDir: string) {
71+
const contents = readFileSync(`${fixtureDir}/.nuxt/types/nuxt-better-auth-server-context.d.ts`, 'utf8')
72+
expect(contents).not.toContain('declare module \'@nuxthub/db\'')
73+
expect(contents).not.toContain('declare module "@nuxthub/db"')
74+
}
75+
76+
function expectNuxtTypesToStayClientSafe(fixtureDir: string) {
77+
const contents = readFileSync(`${fixtureDir}/.nuxt/nuxt.d.ts`, 'utf8')
78+
expect(contents).toContain('types/nuxt-better-auth-config-context.d.ts')
79+
expect(contents).toContain('types/nuxt-better-auth-infer.d.ts')
80+
expect(contents).toContain('types/nuxt-better-auth-social-providers.d.ts')
81+
expect(contents).not.toContain('types/nuxt-better-auth-nitro.d.ts')
82+
}
83+
84+
function expectSharedTypesToIncludeOnlySafeConfigContext(fixtureDir: string) {
85+
const contents = readFileSync(`${fixtureDir}/.nuxt/nuxt.shared.d.ts`, 'utf8')
86+
expect(contents).toContain('types/nuxt-better-auth-config-context.d.ts')
87+
expect(contents).not.toContain('types/nuxt-better-auth-infer.d.ts')
88+
expect(contents).not.toContain('types/nuxt-better-auth-social-providers.d.ts')
89+
expect(contents).not.toContain('types/nuxt-better-auth-nitro.d.ts')
90+
}
91+
4292
describe('server auth config project-reference typecheck regression #309', () => {
4393
it('typechecks a layered auth config that uses Nitro auto-imported helpers', () => {
44-
runProjectReferenceTypecheck(fileURLToPath(new URL('./cases/layer-server-auth-typecheck', import.meta.url)))
94+
const fixtureDir = fileURLToPath(new URL('./cases/layer-server-auth-typecheck', import.meta.url))
95+
runProjectReferenceTypecheck(fixtureDir)
96+
expectSharedTypeReferencesToStayClientSafe(fixtureDir)
97+
expectServerContextToAvoidNuxthubAugmentation(fixtureDir)
98+
expectNuxtTypesToStayClientSafe(fixtureDir)
99+
expectSharedTypesToIncludeOnlySafeConfigContext(fixtureDir)
45100
}, 60_000)
46101

47102
it('typechecks auth config imports that use the #server alias', () => {
48-
runProjectReferenceTypecheck(fileURLToPath(new URL('./cases/server-auth-alias-typecheck', import.meta.url)))
103+
const fixtureDir = fileURLToPath(new URL('./cases/server-auth-alias-typecheck', import.meta.url))
104+
runProjectReferenceTypecheck(fixtureDir)
105+
expectSharedTypeReferencesToStayClientSafe(fixtureDir)
106+
expectServerContextToAvoidNuxthubAugmentation(fixtureDir)
107+
expectNuxtTypesToStayClientSafe(fixtureDir)
108+
expectSharedTypesToIncludeOnlySafeConfigContext(fixtureDir)
49109
}, 60_000)
50110
})

0 commit comments

Comments
 (0)