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

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
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.0.2-alpha.12",
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
@@ -5,7 +5,7 @@ import { consola as consola$1 } from 'consola';
5
5
  import { defu } from 'defu';
6
6
  import { join } from 'pathe';
7
7
  import { toRouteMatcher, createRouter } from 'radix3';
8
- import pluralize from 'pluralize';
8
+ import { generateDrizzleSchema as generateDrizzleSchema$1 } from '@better-auth/cli/api';
9
9
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
10
10
 
11
11
  function setupDevTools(nuxt) {
@@ -23,160 +23,51 @@ function setupDevTools(nuxt) {
23
23
  });
24
24
  }
25
25
 
26
- function toSnakeCase(str) {
27
- return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
26
+ function dialectToProvider(dialect) {
27
+ return dialect === "postgresql" ? "pg" : dialect;
28
28
  }
29
- function generateDrizzleSchema(tables, dialect, options) {
30
- const typedTables = tables;
31
- const imports = getImports(dialect, options);
32
- const tableDefinitions = Object.entries(typedTables).map(([tableName, table]) => generateTable(tableName, table, dialect, typedTables, options)).join("\n\n");
33
- return `${imports}
34
-
35
- ${tableDefinitions}
36
- `;
37
- }
38
- function getImports(dialect, options) {
39
- switch (dialect) {
40
- case "sqlite":
41
- return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'`;
42
- case "postgresql":
43
- return options?.useUuid ? `import { boolean, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'` : `import { boolean, integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core'`;
44
- case "mysql":
45
- return `import { boolean, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'`;
46
- }
47
- }
48
- function generateTable(tableName, table, dialect, allTables, options) {
49
- const tableFunc = dialect === "sqlite" ? "sqliteTable" : dialect === "postgresql" ? "pgTable" : "mysqlTable";
50
- const hasCustomModelName = table.modelName && table.modelName !== tableName;
51
- let dbTableName = hasCustomModelName ? table.modelName : tableName;
52
- if (options?.casing === "snake_case" && !hasCustomModelName)
53
- dbTableName = toSnakeCase(dbTableName);
54
- if (options?.usePlural && !hasCustomModelName)
55
- dbTableName = pluralize(dbTableName);
56
- const fields = Object.entries(table.fields).map(([fieldName, field]) => generateField(fieldName, field, dialect, allTables, options)).join(",\n ");
57
- const idField = generateIdField(dialect, options);
58
- return `export const ${tableName} = ${tableFunc}('${dbTableName}', {
59
- ${idField},
60
- ${fields}
61
- })`;
62
- }
63
- function generateIdField(dialect, options) {
64
- switch (dialect) {
65
- case "sqlite":
66
- if (options?.useUuid)
67
- consola$1.warn("[@onmax/nuxt-better-auth] useUuid ignored for SQLite (no native uuid type). Using text.");
68
- return `id: text('id').primaryKey()`;
69
- case "postgresql":
70
- return options?.useUuid ? `id: uuid('id').defaultRandom().primaryKey()` : `id: text('id').primaryKey()`;
71
- case "mysql":
72
- return `id: varchar('id', { length: 36 }).primaryKey()`;
73
- }
74
- }
75
- function generateField(fieldName, field, dialect, allTables, options) {
76
- const dbFieldName = options?.casing === "snake_case" ? toSnakeCase(fieldName) : fieldName;
77
- const isFkToId = options?.useUuid && field.references?.field === "id";
78
- let fieldDef;
79
- if (isFkToId && dialect === "postgresql")
80
- fieldDef = `uuid('${dbFieldName}')`;
81
- else if (isFkToId && dialect === "mysql")
82
- fieldDef = `varchar('${dbFieldName}', { length: 36 })`;
83
- else
84
- fieldDef = getFieldType(field.type, dialect, dbFieldName);
85
- if (field.required && field.defaultValue === void 0)
86
- fieldDef += ".notNull()";
87
- if (field.unique)
88
- fieldDef += ".unique()";
89
- if (field.defaultValue !== void 0) {
90
- if (typeof field.defaultValue === "boolean")
91
- fieldDef += `.default(${field.defaultValue})`;
92
- else if (typeof field.defaultValue === "string")
93
- fieldDef += `.default('${field.defaultValue}')`;
94
- else if (typeof field.defaultValue === "function")
95
- fieldDef += `.$defaultFn(${field.defaultValue})`;
96
- else
97
- fieldDef += `.default(${field.defaultValue})`;
98
- if (field.required)
99
- fieldDef += ".notNull()";
100
- }
101
- if (typeof field.onUpdate === "function" && field.type === "date")
102
- fieldDef += `.$onUpdate(${field.onUpdate})`;
103
- if (field.references) {
104
- const refTable = field.references.model;
105
- if (allTables[refTable])
106
- fieldDef += `.references(() => ${refTable}.${field.references.field})`;
107
- }
108
- return `${fieldName}: ${fieldDef}`;
109
- }
110
- function getFieldType(type, dialect, fieldName) {
111
- const normalizedType = Array.isArray(type) ? "string" : type;
112
- switch (dialect) {
113
- case "sqlite":
114
- return getSqliteType(normalizedType, fieldName);
115
- case "postgresql":
116
- return getPostgresType(normalizedType, fieldName);
117
- case "mysql":
118
- return getMysqlType(normalizedType, fieldName);
119
- }
120
- }
121
- function getSqliteType(type, fieldName) {
122
- switch (type) {
123
- case "string":
124
- return `text('${fieldName}')`;
125
- case "boolean":
126
- return `integer('${fieldName}', { mode: 'boolean' })`;
127
- case "date":
128
- return `integer('${fieldName}', { mode: 'timestamp' })`;
129
- case "number":
130
- return `integer('${fieldName}')`;
131
- default:
132
- return `text('${fieldName}')`;
133
- }
134
- }
135
- function getPostgresType(type, fieldName) {
136
- switch (type) {
137
- case "string":
138
- return `text('${fieldName}')`;
139
- case "boolean":
140
- return `boolean('${fieldName}')`;
141
- case "date":
142
- return `timestamp('${fieldName}')`;
143
- case "number":
144
- return `integer('${fieldName}')`;
145
- default:
146
- return `text('${fieldName}')`;
147
- }
148
- }
149
- function getMysqlType(type, fieldName) {
150
- switch (type) {
151
- case "string":
152
- return `text('${fieldName}')`;
153
- case "boolean":
154
- return `boolean('${fieldName}')`;
155
- case "date":
156
- return `timestamp('${fieldName}')`;
157
- case "number":
158
- return `int('${fieldName}')`;
159
- default:
160
- return `text('${fieldName}')`;
29
+ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
30
+ const provider = dialectToProvider(dialect);
31
+ const options = {
32
+ ...authOptions,
33
+ advanced: {
34
+ ...authOptions.advanced,
35
+ database: {
36
+ ...authOptions.advanced?.database,
37
+ ...schemaOptions?.useUuid && { generateId: "uuid" }
38
+ }
39
+ }
40
+ };
41
+ const adapter = {
42
+ id: "drizzle",
43
+ options: {
44
+ provider,
45
+ camelCase: schemaOptions?.casing !== "snake_case",
46
+ adapterConfig: { usePlural: schemaOptions?.usePlural ?? false }
47
+ }
48
+ };
49
+ const result = await generateDrizzleSchema$1({ adapter, options });
50
+ if (!result.code) {
51
+ throw new Error(`Schema generation returned empty result for ${dialect}`);
161
52
  }
53
+ return result.code;
162
54
  }
163
55
  async function loadUserAuthConfig(configPath, throwOnError = false) {
164
56
  const { createJiti } = await import('jiti');
165
57
  const { defineServerAuth } = await import('../dist/runtime/config.js');
166
58
  const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
167
- const key = "defineServerAuth";
168
- const g = globalThis;
169
- if (!g[key]) {
59
+ if (!globalThis.defineServerAuth) {
170
60
  defineServerAuth._count = 0;
171
- g[key] = defineServerAuth;
61
+ globalThis.defineServerAuth = defineServerAuth;
172
62
  }
173
- g[key]._count++;
63
+ globalThis.defineServerAuth._count++;
174
64
  try {
175
65
  const mod = await jiti.import(configPath);
176
66
  const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
177
67
  if (typeof configFn === "function") {
178
68
  return configFn({ runtimeConfig: {}, db: null });
179
69
  }
70
+ consola$1.warn("[@onmax/nuxt-better-auth] auth.config.ts does not export a function. Expected: export default defineServerAuth(...)");
180
71
  if (throwOnError) {
181
72
  throw new Error("auth.config.ts must export default defineServerAuth(...)");
182
73
  }
@@ -188,9 +79,9 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
188
79
  consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
189
80
  return {};
190
81
  } finally {
191
- g[key]._count--;
192
- if (!g[key]._count) {
193
- delete g[key];
82
+ globalThis.defineServerAuth._count--;
83
+ if (!globalThis.defineServerAuth._count) {
84
+ globalThis.defineServerAuth = void 0;
194
85
  }
195
86
  }
196
87
  }
@@ -257,14 +148,15 @@ const module$1 = defineNuxtModule({
257
148
  consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
258
149
  }
259
150
  if (!clientOnly) {
260
- const betterAuthSecret = process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || nuxt.options.runtimeConfig.betterAuthSecret || "";
151
+ const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
152
+ nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
153
+ const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
261
154
  if (!nuxt.options.dev && !betterAuthSecret) {
262
155
  throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
263
156
  }
264
157
  if (betterAuthSecret && betterAuthSecret.length < 32) {
265
158
  throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
266
159
  }
267
- nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
268
160
  nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
269
161
  secondaryStorage: secondaryStorageEnabled
270
162
  });
@@ -312,7 +204,7 @@ declare module '#auth/secondary-storage' {
312
204
  export function createSecondaryStorage(): SecondaryStorage | undefined
313
205
  }
314
206
  `
315
- });
207
+ }, { nitro: true, node: true });
316
208
  addTypeTemplate({
317
209
  filename: "types/auth-database.d.ts",
318
210
  getContents: () => `
@@ -322,7 +214,7 @@ declare module '#auth/database' {
322
214
  export const db: unknown
323
215
  }
324
216
  `
325
- });
217
+ }, { nitro: true, node: true });
326
218
  addTypeTemplate({
327
219
  filename: "types/nuxt-better-auth-infer.d.ts",
328
220
  getContents: () => `
@@ -354,7 +246,7 @@ declare module '@onmax/nuxt-better-auth/config' {
354
246
  export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => T): (ctx: _AugmentedServerAuthContext) => T
355
247
  }
356
248
  `
357
- });
249
+ }, { nuxt: true, nitro: true, node: true });
358
250
  addTypeTemplate({
359
251
  filename: "types/nuxt-better-auth-nitro.d.ts",
360
252
  getContents: () => `
@@ -465,27 +357,22 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
465
357
  const extendedConfig = {};
466
358
  await nuxt.callHook("better-auth:config:extend", extendedConfig);
467
359
  const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
468
- const { getAuthTables } = await import('better-auth/db');
469
- const tables = getAuthTables({
360
+ const authOptions = {
361
+ ...userConfig,
470
362
  plugins,
471
- secondaryStorage: options.secondaryStorage ? {
472
- get: async (_key) => null,
473
- set: async (_key, _value, _ttl) => {
474
- },
475
- delete: async (_key) => {
476
- }
477
- } : void 0
478
- });
479
- const useUuid = userConfig.advanced?.database?.generateId === "uuid";
363
+ secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
364
+ }, delete: async (_key) => {
365
+ } } : void 0
366
+ };
480
367
  const hubCasing = getHubCasing(hub);
481
- const schemaOptions = { ...options.schema, useUuid, casing: options.schema?.casing ?? hubCasing };
482
- const schemaCode = generateDrizzleSchema(tables, dialect, schemaOptions);
368
+ const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
369
+ const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
483
370
  const schemaDir = join(nuxt.options.buildDir, "better-auth");
484
371
  const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
485
372
  await mkdir(schemaDir, { recursive: true });
486
373
  await writeFile(schemaPath, schemaCode);
487
374
  addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
488
- consola.info(`Generated ${dialect} schema with ${Object.keys(tables).length} tables`);
375
+ consola.info(`Generated ${dialect} schema`);
489
376
  } catch (error) {
490
377
  if (isProduction) {
491
378
  throw error;
@@ -1,9 +1,9 @@
1
1
  import { defineEventHandler } from "h3";
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { serverAuth } from "../../utils/auth.js";
4
- export default defineEventHandler(async () => {
4
+ export default defineEventHandler(async (event) => {
5
5
  try {
6
- const auth = serverAuth();
6
+ const auth = serverAuth(event);
7
7
  const options = auth.options;
8
8
  const runtimeConfig = useRuntimeConfig();
9
9
  const publicAuth = runtimeConfig.public?.auth;
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, toWebRequest } from "h3";
2
2
  import { serverAuth } from "../../utils/auth.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const auth = serverAuth();
4
+ const auth = serverAuth(event);
5
5
  return auth.handler(toWebRequest(event));
6
6
  });
@@ -1,5 +1,7 @@
1
1
  import type { Auth } from 'better-auth';
2
+ import type { H3Event } from 'h3';
2
3
  import createServerAuth from '#auth/server';
3
4
  type AuthInstance = Auth<ReturnType<typeof createServerAuth>>;
4
- export declare function serverAuth(): AuthInstance;
5
+ /** Returns Better Auth instance. Pass event for accurate URL detection on first call. */
6
+ export declare function serverAuth(event?: H3Event): AuthInstance;
5
7
  export {};
@@ -2,15 +2,72 @@ import { createDatabase, db } from "#auth/database";
2
2
  import { createSecondaryStorage } from "#auth/secondary-storage";
3
3
  import createServerAuth from "#auth/server";
4
4
  import { betterAuth } from "better-auth";
5
+ import { getRequestHost, getRequestProtocol } from "h3";
5
6
  import { useRuntimeConfig } from "nitropack/runtime";
7
+ import { withoutProtocol } from "ufo";
6
8
  let _auth = null;
7
- export function serverAuth() {
9
+ function validateURL(url) {
10
+ try {
11
+ return new URL(url).origin;
12
+ } catch {
13
+ throw new Error(`Invalid siteUrl: "${url}". Must be a valid URL.`);
14
+ }
15
+ }
16
+ function getNitroOrigin(e) {
17
+ const cert = process.env.NITRO_SSL_CERT;
18
+ const key = process.env.NITRO_SSL_KEY;
19
+ let host = process.env.NITRO_HOST || process.env.HOST;
20
+ let port;
21
+ if (import.meta.dev)
22
+ port = process.env.NITRO_PORT || process.env.PORT || "3000";
23
+ let protocol = cert && key || !import.meta.dev ? "https" : "http";
24
+ try {
25
+ if ((import.meta.dev || import.meta.prerender) && process.env.__NUXT_DEV__) {
26
+ const origin = JSON.parse(process.env.__NUXT_DEV__).proxy.url;
27
+ host = withoutProtocol(origin);
28
+ protocol = origin.includes("https") ? "https" : "http";
29
+ } else if ((import.meta.dev || import.meta.prerender) && process.env.NUXT_VITE_NODE_OPTIONS) {
30
+ const origin = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL.replace("/__nuxt_vite_node__", "");
31
+ host = withoutProtocol(origin);
32
+ protocol = origin.includes("https") ? "https" : "http";
33
+ } else if (e) {
34
+ host = getRequestHost(e, { xForwardedHost: true }) || host;
35
+ protocol = getRequestProtocol(e, { xForwardedProto: true }) || protocol;
36
+ }
37
+ } catch {
38
+ }
39
+ if (!host)
40
+ return void 0;
41
+ if (host.includes(":") && !host.startsWith("[")) {
42
+ const hostParts = host.split(":");
43
+ port = hostParts.pop();
44
+ host = hostParts.join(":");
45
+ }
46
+ const portSuffix = port ? `:${port}` : "";
47
+ return `${protocol}://${host}${portSuffix}`;
48
+ }
49
+ function getBaseURL(event) {
50
+ const config = useRuntimeConfig();
51
+ if (config.public.siteUrl && typeof config.public.siteUrl === "string")
52
+ return validateURL(config.public.siteUrl);
53
+ const nitroOrigin = getNitroOrigin(event);
54
+ if (nitroOrigin)
55
+ return validateURL(nitroOrigin);
56
+ if (process.env.VERCEL_URL)
57
+ return validateURL(`https://${process.env.VERCEL_URL}`);
58
+ if (process.env.CF_PAGES_URL)
59
+ return validateURL(`https://${process.env.CF_PAGES_URL}`);
60
+ if (process.env.URL)
61
+ return validateURL(process.env.URL.startsWith("http") ? process.env.URL : `https://${process.env.URL}`);
62
+ if (import.meta.dev)
63
+ return "http://localhost:3000";
64
+ throw new Error("siteUrl required. Set NUXT_PUBLIC_SITE_URL.");
65
+ }
66
+ export function serverAuth(event) {
8
67
  if (_auth)
9
68
  return _auth;
10
69
  const runtimeConfig = useRuntimeConfig();
11
- const siteUrl = runtimeConfig.public.siteUrl;
12
- if (!siteUrl)
13
- throw new Error("siteUrl must be configured. Set NUXT_PUBLIC_SITE_URL or configure in nuxt.config.");
70
+ const siteUrl = getBaseURL(event);
14
71
  const database = createDatabase();
15
72
  const userConfig = createServerAuth({ runtimeConfig, db });
16
73
  _auth = betterAuth({
@@ -2,7 +2,7 @@ import { createError } from "h3";
2
2
  import { matchesUser } from "../../utils/match-user.js";
3
3
  import { serverAuth } from "./auth.js";
4
4
  export async function getUserSession(event) {
5
- const auth = serverAuth();
5
+ const auth = serverAuth(event);
6
6
  const session = await auth.api.getSession({ headers: event.headers });
7
7
  return session;
8
8
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@onmax/nuxt-better-auth",
3
3
  "type": "module",
4
- "version": "0.0.2-alpha.12",
4
+ "version": "0.0.2-alpha.14",
5
5
  "packageManager": "pnpm@10.15.1",
6
6
  "description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
7
7
  "author": "onmax",
@@ -10,6 +10,14 @@
10
10
  "type": "git",
11
11
  "url": "https://github.com/onmax/nuxt-better-auth"
12
12
  },
13
+ "agents": {
14
+ "skills": [
15
+ {
16
+ "name": "nuxt-better-auth",
17
+ "path": "./skills/nuxt-better-auth"
18
+ }
19
+ ]
20
+ },
13
21
  "exports": {
14
22
  ".": {
15
23
  "types": "./dist/types.d.mts",
@@ -32,7 +40,8 @@
32
40
  }
33
41
  },
34
42
  "files": [
35
- "dist"
43
+ "dist",
44
+ "skills"
36
45
  ],
37
46
  "scripts": {
38
47
  "prepack": "nuxt-module-build build",
@@ -59,16 +68,15 @@
59
68
  }
60
69
  },
61
70
  "dependencies": {
71
+ "@better-auth/cli": "^1.5.0-beta.3",
62
72
  "@nuxt/kit": "^4.2.2",
63
73
  "defu": "^6.1.4",
64
74
  "jiti": "^2.4.2",
65
75
  "pathe": "^2.0.3",
66
- "pluralize": "^8.0.0",
67
76
  "radix3": "^1.1.2"
68
77
  },
69
78
  "devDependencies": {
70
79
  "@antfu/eslint-config": "^4.12.0",
71
- "@better-auth/cli": "^1.4.6",
72
80
  "@libsql/client": "^0.15.15",
73
81
  "@nuxt/devtools": "^3.1.1",
74
82
  "@nuxt/devtools-kit": "^3.1.1",
@@ -78,8 +86,7 @@
78
86
  "@nuxthub/core": "^0.10.3",
79
87
  "@types/better-sqlite3": "^7.6.13",
80
88
  "@types/node": "latest",
81
- "@types/pluralize": "^0.0.33",
82
- "better-auth": "^1.4.7",
89
+ "better-auth": "^1.5.0-beta.3",
83
90
  "better-sqlite3": "^11.9.1",
84
91
  "bumpp": "^10.3.2",
85
92
  "changelogen": "^0.6.2",
@@ -87,6 +94,7 @@
87
94
  "drizzle-kit": "^0.31.8",
88
95
  "drizzle-orm": "^0.38.4",
89
96
  "eslint": "^9.39.1",
97
+ "npm-agentskills": "https://pkg.pr.new/onmax/npm-agentskills@394499e",
90
98
  "nuxt": "^4.2.2",
91
99
  "tinyexec": "^1.0.2",
92
100
  "typescript": "~5.9.3",
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: nuxt-better-auth
3
+ description: Use when implementing auth in Nuxt apps with @onmax/nuxt-better-auth - provides useUserSession composable, server auth helpers, route protection, and Better Auth plugins integration.
4
+ license: MIT
5
+ ---
6
+
7
+ # Nuxt Better Auth
8
+
9
+ Authentication module for Nuxt 4+ built on [Better Auth](https://www.better-auth.com/). Provides composables, server utilities, and route protection.
10
+
11
+ > **Alpha Status**: This module is currently in alpha (v0.0.2-alpha.12) and not recommended for production use. APIs may change.
12
+
13
+ ## When to Use
14
+
15
+ - Installing/configuring `@onmax/nuxt-better-auth`
16
+ - Implementing login/signup/signout flows
17
+ - Protecting routes (client and server)
18
+ - Accessing user session in API routes
19
+ - Integrating Better Auth plugins (admin, passkey, 2FA)
20
+ - Setting up database with NuxtHub
21
+ - Using clientOnly mode for external auth backends
22
+
23
+ **For Nuxt patterns:** use `nuxt` skill
24
+ **For NuxtHub database:** use `nuxthub` skill
25
+
26
+ ## Available Guidance
27
+
28
+ | File | Topics |
29
+ | -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
30
+ | **[references/installation.md](references/installation.md)** | Module setup, env vars, config files |
31
+ | **[references/client-auth.md](references/client-auth.md)** | useUserSession, signIn/signUp/signOut, BetterAuthState, safe redirects |
32
+ | **[references/server-auth.md](references/server-auth.md)** | serverAuth, getUserSession, requireUserSession |
33
+ | **[references/route-protection.md](references/route-protection.md)** | routeRules, definePageMeta, middleware |
34
+ | **[references/plugins.md](references/plugins.md)** | Better Auth plugins (admin, passkey, 2FA) |
35
+ | **[references/database.md](references/database.md)** | NuxtHub integration, Drizzle schema |
36
+ | **[references/client-only.md](references/client-only.md)** | External auth backend, clientOnly mode, CORS |
37
+ | **[references/types.md](references/types.md)** | AuthUser, AuthSession, type augmentation |
38
+
39
+ ## Usage Pattern
40
+
41
+ **Load based on context:**
42
+
43
+ - Installing module? → [references/installation.md](references/installation.md)
44
+ - Login/signup forms? → [references/client-auth.md](references/client-auth.md)
45
+ - API route protection? → [references/server-auth.md](references/server-auth.md)
46
+ - Route rules/page meta? → [references/route-protection.md](references/route-protection.md)
47
+ - Using plugins? → [references/plugins.md](references/plugins.md)
48
+ - Database setup? → [references/database.md](references/database.md)
49
+ - External auth backend? → [references/client-only.md](references/client-only.md)
50
+ - TypeScript types? → [references/types.md](references/types.md)
51
+
52
+ **DO NOT read all files at once.** Load based on context.
53
+
54
+ ## Key Concepts
55
+
56
+ | Concept | Description |
57
+ | ---------------------- | --------------------------------------------------------------- |
58
+ | `useUserSession()` | Client composable - user, session, loggedIn, signIn/Out methods |
59
+ | `requireUserSession()` | Server helper - throws 401/403 if not authenticated |
60
+ | `auth` route mode | `'user'`, `'guest'`, `{ user: {...} }`, or `false` |
61
+ | `serverAuth()` | Get Better Auth instance in server routes |
62
+
63
+ ## Quick Reference
64
+
65
+ ```ts
66
+ // Client: useUserSession()
67
+ const { user, loggedIn, signIn, signOut } = useUserSession()
68
+ await signIn.email({ email, password }, { onSuccess: () => navigateTo('/') })
69
+ ```
70
+
71
+ ```ts
72
+ // Server: requireUserSession()
73
+ const { user } = await requireUserSession(event, { user: { role: 'admin' } })
74
+ ```
75
+
76
+ ```ts
77
+ // nuxt.config.ts: Route protection
78
+ routeRules: {
79
+ '/admin/**': { auth: { user: { role: 'admin' } } },
80
+ '/login': { auth: 'guest' },
81
+ '/app/**': { auth: 'user' }
82
+ }
83
+ ```
84
+
85
+ ## Resources
86
+
87
+ - [Module Docs](https://github.com/onmax/nuxt-better-auth)
88
+ - [Better Auth Docs](https://www.better-auth.com/)
89
+
90
+ ---
91
+
92
+ _Token efficiency: Main skill ~300 tokens, each sub-file ~800-1200 tokens_