@onmax/nuxt-better-auth 0.0.2-alpha.13 → 0.0.2-alpha.15

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.15",
3
4
  "configKey": "auth",
4
5
  "compatibility": {
5
6
  "nuxt": ">=3.0.0"
6
7
  },
7
- "version": "0.0.2-alpha.13",
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,17 @@
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';
8
- import pluralize from 'pluralize';
9
+ import { isCI, isTest } from 'std-env';
10
+ import { generateDrizzleSchema as generateDrizzleSchema$1 } from '@better-auth/cli/api';
9
11
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
10
12
 
13
+ const version = "0.0.2-alpha.15";
14
+
11
15
  function setupDevTools(nuxt) {
12
16
  nuxt.hook("devtools:customTabs", (tabs) => {
13
17
  tabs.push({
@@ -23,160 +27,51 @@ function setupDevTools(nuxt) {
23
27
  });
24
28
  }
25
29
 
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();
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}`;
30
+ function dialectToProvider(dialect) {
31
+ return dialect === "postgresql" ? "pg" : dialect;
109
32
  }
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}')`;
33
+ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
34
+ const provider = dialectToProvider(dialect);
35
+ const options = {
36
+ ...authOptions,
37
+ advanced: {
38
+ ...authOptions.advanced,
39
+ database: {
40
+ ...authOptions.advanced?.database,
41
+ ...schemaOptions?.useUuid && { generateId: "uuid" }
42
+ }
43
+ }
44
+ };
45
+ const adapter = {
46
+ id: "drizzle",
47
+ options: {
48
+ provider,
49
+ camelCase: schemaOptions?.casing !== "snake_case",
50
+ adapterConfig: { usePlural: schemaOptions?.usePlural ?? false }
51
+ }
52
+ };
53
+ const result = await generateDrizzleSchema$1({ adapter, options });
54
+ if (!result.code) {
55
+ throw new Error(`Schema generation returned empty result for ${dialect}`);
161
56
  }
57
+ return result.code;
162
58
  }
163
59
  async function loadUserAuthConfig(configPath, throwOnError = false) {
164
60
  const { createJiti } = await import('jiti');
165
61
  const { defineServerAuth } = await import('../dist/runtime/config.js');
166
62
  const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
167
- const key = "defineServerAuth";
168
- const g = globalThis;
169
- if (!g[key]) {
63
+ if (!globalThis.defineServerAuth) {
170
64
  defineServerAuth._count = 0;
171
- g[key] = defineServerAuth;
65
+ globalThis.defineServerAuth = defineServerAuth;
172
66
  }
173
- g[key]._count++;
67
+ globalThis.defineServerAuth._count++;
174
68
  try {
175
69
  const mod = await jiti.import(configPath);
176
- const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
70
+ const configFn = mod.default;
177
71
  if (typeof configFn === "function") {
178
72
  return configFn({ runtimeConfig: {}, db: null });
179
73
  }
74
+ consola$1.warn("[@onmax/nuxt-better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)");
180
75
  if (throwOnError) {
181
76
  throw new Error("auth.config.ts must export default defineServerAuth(...)");
182
77
  }
@@ -188,9 +83,9 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
188
83
  consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
189
84
  return {};
190
85
  } finally {
191
- g[key]._count--;
192
- if (!g[key]._count) {
193
- delete g[key];
86
+ globalThis.defineServerAuth._count--;
87
+ if (!globalThis.defineServerAuth._count) {
88
+ globalThis.defineServerAuth = void 0;
194
89
  }
195
90
  }
196
91
  }
@@ -210,8 +105,72 @@ function getHubCasing(hub) {
210
105
  return hub.db.casing;
211
106
  }
212
107
  const consola = consola$1.withTag("nuxt-better-auth");
108
+ const generateSecret = () => randomBytes(32).toString("hex");
109
+ function readEnvFile(rootDir) {
110
+ const envPath = join(rootDir, ".env");
111
+ return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
112
+ }
113
+ function hasEnvSecret(rootDir) {
114
+ const match = readEnvFile(rootDir).match(/^BETTER_AUTH_SECRET=(.+)$/m);
115
+ return !!match && !!match[1] && match[1].trim().length > 0;
116
+ }
117
+ function appendSecretToEnv(rootDir, secret) {
118
+ const envPath = join(rootDir, ".env");
119
+ let content = readEnvFile(rootDir);
120
+ if (content.length > 0 && !content.endsWith("\n"))
121
+ content += "\n";
122
+ content += `BETTER_AUTH_SECRET=${secret}
123
+ `;
124
+ writeFileSync(envPath, content, "utf-8");
125
+ }
126
+ async function promptForSecret(rootDir) {
127
+ if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
128
+ return void 0;
129
+ if (isCI || isTest) {
130
+ const secret2 = generateSecret();
131
+ appendSecretToEnv(rootDir, secret2);
132
+ consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI mode)");
133
+ return secret2;
134
+ }
135
+ consola.box("BETTER_AUTH_SECRET is required for authentication.\nThis will be appended to your .env file.");
136
+ const choice = await consola.prompt("How do you want to set it?", {
137
+ type: "select",
138
+ options: [
139
+ { label: "Generate for me", value: "generate", hint: "uses crypto.randomBytes(32)" },
140
+ { label: "Enter manually", value: "paste" },
141
+ { label: "Skip", value: "skip", hint: "will fail in production" }
142
+ ],
143
+ cancel: "null"
144
+ });
145
+ if (typeof choice === "symbol" || choice === "skip") {
146
+ consola.warn("Skipping BETTER_AUTH_SECRET. Auth will fail without it in production.");
147
+ return void 0;
148
+ }
149
+ let secret;
150
+ if (choice === "generate") {
151
+ secret = generateSecret();
152
+ } else {
153
+ const input = await consola.prompt("Paste your secret (min 32 chars):", { type: "text", cancel: "null" });
154
+ if (typeof input === "symbol" || !input || input.length < 32) {
155
+ consola.warn("Invalid secret. Skipping.");
156
+ return void 0;
157
+ }
158
+ secret = input;
159
+ }
160
+ const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
161
+ const confirm = await consola.prompt(`Add to .env:
162
+ BETTER_AUTH_SECRET=${preview}
163
+ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
164
+ if (typeof confirm === "symbol" || !confirm) {
165
+ consola.info("Cancelled. Secret not written.");
166
+ return void 0;
167
+ }
168
+ appendSecretToEnv(rootDir, secret);
169
+ consola.success("Added BETTER_AUTH_SECRET to .env");
170
+ return secret;
171
+ }
213
172
  const module$1 = defineNuxtModule({
214
- meta: { name: "@onmax/nuxt-better-auth", configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
173
+ meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
215
174
  defaults: {
216
175
  clientOnly: false,
217
176
  serverConfig: "server/auth.config",
@@ -219,8 +178,40 @@ const module$1 = defineNuxtModule({
219
178
  redirects: { login: "/login", guest: "/" },
220
179
  secondaryStorage: false
221
180
  },
181
+ async onInstall(nuxt) {
182
+ const generatedSecret = await promptForSecret(nuxt.options.rootDir);
183
+ if (generatedSecret)
184
+ process.env.BETTER_AUTH_SECRET = generatedSecret;
185
+ const serverPath = join(nuxt.options.rootDir, "server/auth.config.ts");
186
+ const clientPath = join(nuxt.options.srcDir, "auth.config.ts");
187
+ const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
188
+
189
+ export default defineServerAuth({
190
+ emailAndPassword: { enabled: true },
191
+ })
192
+ `;
193
+ const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
194
+
195
+ export default defineClientAuth({})
196
+ `;
197
+ if (!existsSync(serverPath)) {
198
+ await mkdir(dirname(serverPath), { recursive: true });
199
+ await writeFile(serverPath, serverTemplate);
200
+ consola.success("Created server/auth.config.ts");
201
+ }
202
+ if (!existsSync(clientPath)) {
203
+ await mkdir(dirname(clientPath), { recursive: true });
204
+ await writeFile(clientPath, clientTemplate);
205
+ const relativePath = clientPath.replace(`${nuxt.options.rootDir}/`, "");
206
+ consola.success(`Created ${relativePath}`);
207
+ }
208
+ },
222
209
  async setup(options, nuxt) {
223
210
  const resolver = createResolver(import.meta.url);
211
+ if (options.clientConfig === "app/auth.config") {
212
+ const srcDirRelative = nuxt.options.srcDir.replace(`${nuxt.options.rootDir}/`, "");
213
+ options.clientConfig = srcDirRelative === nuxt.options.srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
214
+ }
224
215
  const clientOnly = options.clientOnly;
225
216
  const serverConfigFile = options.serverConfig;
226
217
  const clientConfigFile = options.clientConfig;
@@ -229,9 +220,9 @@ const module$1 = defineNuxtModule({
229
220
  const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
230
221
  const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
231
222
  if (!clientOnly && !serverConfigExists)
232
- throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - create with defineServerAuth()`);
223
+ throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
233
224
  if (!clientConfigExists)
234
- throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export createAppAuthClient()`);
225
+ throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
235
226
  const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
236
227
  const hub = hasNuxtHub ? nuxt.options.hub : void 0;
237
228
  const hasHubDb = !clientOnly && hasNuxtHub && !!hub?.db;
@@ -257,14 +248,15 @@ const module$1 = defineNuxtModule({
257
248
  consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
258
249
  }
259
250
  if (!clientOnly) {
260
- const betterAuthSecret = process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || nuxt.options.runtimeConfig.betterAuthSecret || "";
261
- if (!nuxt.options.dev && !betterAuthSecret) {
251
+ const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
252
+ nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
253
+ const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
254
+ if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
262
255
  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
256
  }
264
257
  if (betterAuthSecret && betterAuthSecret.length < 32) {
265
258
  throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
266
259
  }
267
- nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
268
260
  nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
269
261
  secondaryStorage: secondaryStorageEnabled
270
262
  });
@@ -291,11 +283,13 @@ export function createSecondaryStorage() {
291
283
  throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
292
284
  }
293
285
  const hubDialect = getHubDialect(hub) ?? "sqlite";
286
+ const usePlural = options.schema?.usePlural ?? false;
287
+ const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
294
288
  const databaseCode = hasHubDb ? `import { db, schema } from '../hub/db.mjs'
295
289
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
296
290
  const rawDialect = '${hubDialect}'
297
291
  const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
298
- export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
292
+ export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${usePlural}, camelCase: ${camelCase} }) }
299
293
  export { db }` : `export function createDatabase() { return undefined }
300
294
  export const db = undefined`;
301
295
  const databaseTemplate = addTemplate({ filename: "better-auth/database.mjs", getContents: () => databaseCode, write: true });
@@ -328,9 +322,9 @@ declare module '#auth/database' {
328
322
  getContents: () => `
329
323
  import type { InferUser, InferSession, InferPluginTypes } from 'better-auth'
330
324
  import type { RuntimeConfig } from 'nuxt/schema'
331
- import type configFn from '${serverConfigPath}'
325
+ import type createServerAuth from '${serverConfigPath}'
332
326
 
333
- type _Config = ReturnType<typeof configFn>
327
+ type _Config = ReturnType<typeof createServerAuth>
334
328
 
335
329
  declare module '#nuxt-better-auth' {
336
330
  interface AuthUser extends InferUser<_Config> {}
@@ -351,7 +345,7 @@ interface _AugmentedServerAuthContext {
351
345
  declare module '@onmax/nuxt-better-auth/config' {
352
346
  import type { BetterAuthOptions } from 'better-auth'
353
347
  type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>
354
- export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => T): (ctx: _AugmentedServerAuthContext) => T
348
+ export function defineServerAuth<T extends ServerAuthConfig>(config: T | ((ctx: _AugmentedServerAuthContext) => T)): (ctx: _AugmentedServerAuthContext) => T
355
349
  }
356
350
  `
357
351
  }, { nuxt: true, nitro: true, node: true });
@@ -388,7 +382,7 @@ export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, RequireSessionOptio
388
382
  addTypeTemplate({
389
383
  filename: "types/nuxt-better-auth-client.d.ts",
390
384
  getContents: () => `
391
- import type { createAppAuthClient } from '${clientConfigPath}'
385
+ import type createAppAuthClient from '${clientConfigPath}'
392
386
  declare module '#nuxt-better-auth' {
393
387
  export type AppAuthClient = ReturnType<typeof createAppAuthClient>
394
388
  }
@@ -419,6 +413,8 @@ declare module '#nuxt-better-auth' {
419
413
  }
420
414
  const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
421
415
  if (!isProduction && !clientOnly) {
416
+ if (!hasNuxtModule("@nuxt/ui"))
417
+ await installModule("@nuxt/ui");
422
418
  setupDevTools(nuxt);
423
419
  addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
424
420
  if (hasHubDb) {
@@ -465,27 +461,22 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
465
461
  const extendedConfig = {};
466
462
  await nuxt.callHook("better-auth:config:extend", extendedConfig);
467
463
  const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
468
- const { getAuthTables } = await import('better-auth/db');
469
- const tables = getAuthTables({
464
+ const authOptions = {
465
+ ...userConfig,
470
466
  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";
467
+ secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
468
+ }, delete: async (_key) => {
469
+ } } : void 0
470
+ };
480
471
  const hubCasing = getHubCasing(hub);
481
- const schemaOptions = { ...options.schema, useUuid, casing: options.schema?.casing ?? hubCasing };
482
- const schemaCode = generateDrizzleSchema(tables, dialect, schemaOptions);
472
+ const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
473
+ const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
483
474
  const schemaDir = join(nuxt.options.buildDir, "better-auth");
484
475
  const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
485
476
  await mkdir(schemaDir, { recursive: true });
486
477
  await writeFile(schemaPath, schemaCode);
487
478
  addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
488
- consola.info(`Generated ${dialect} schema with ${Object.keys(tables).length} tables`);
479
+ consola.info(`Generated ${dialect} schema`);
489
480
  } catch (error) {
490
481
  if (isProduction) {
491
482
  throw error;
@@ -1,4 +1,4 @@
1
- import { createAppAuthClient } from "#auth/client";
1
+ import createAppAuthClient from "#auth/client";
2
2
  import { computed, nextTick, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
3
3
  let _client = null;
4
4
  function getClient(baseURL) {
@@ -2,12 +2,13 @@ import type { BetterAuthOptions } from 'better-auth';
2
2
  import type { ClientOptions } from 'better-auth/client';
3
3
  import type { CasingOption } from '../schema-generator.js';
4
4
  import type { ServerAuthContext } from './types/augment.js';
5
+ import { createAuthClient } from 'better-auth/vue';
5
6
  export type { ServerAuthContext };
6
7
  export interface ClientAuthContext {
7
8
  siteUrl: string;
8
9
  }
9
- type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>;
10
- type ClientAuthConfig = Omit<ClientOptions, 'baseURL'> & {
10
+ export type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>;
11
+ export type ClientAuthConfig = Omit<ClientOptions, 'baseURL'> & {
11
12
  baseURL?: string;
12
13
  };
13
14
  export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
@@ -44,5 +45,5 @@ export interface AuthRuntimeConfig {
44
45
  export interface AuthPrivateRuntimeConfig {
45
46
  secondaryStorage: boolean;
46
47
  }
47
- export declare function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => T): (ctx: ServerAuthContext) => T;
48
- export declare function defineClientAuth(config: ClientAuthConfigFn): ClientAuthConfigFn;
48
+ export declare function defineServerAuth<T extends ServerAuthConfig>(config: T | ((ctx: ServerAuthContext) => T)): (ctx: ServerAuthContext) => T;
49
+ export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
@@ -1,6 +1,11 @@
1
+ import { createAuthClient } from "better-auth/vue";
1
2
  export function defineServerAuth(config) {
2
- return config;
3
+ return typeof config === "function" ? config : () => config;
3
4
  }
4
5
  export function defineClientAuth(config) {
5
- return config;
6
+ return (baseURL) => {
7
+ const ctx = { siteUrl: baseURL };
8
+ const resolved = typeof config === "function" ? config(ctx) : config;
9
+ return createAuthClient({ ...resolved, baseURL });
10
+ };
6
11
  }
@@ -2,8 +2,9 @@ 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 { getRequestURL } from "h3";
5
+ import { getRequestHost, getRequestProtocol } from "h3";
6
6
  import { useRuntimeConfig } from "nitropack/runtime";
7
+ import { withoutProtocol } from "ufo";
7
8
  let _auth = null;
8
9
  function validateURL(url) {
9
10
  try {
@@ -12,12 +13,46 @@ function validateURL(url) {
12
13
  throw new Error(`Invalid siteUrl: "${url}". Must be a valid URL.`);
13
14
  }
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
+ }
15
49
  function getBaseURL(event) {
16
50
  const config = useRuntimeConfig();
17
51
  if (config.public.siteUrl && typeof config.public.siteUrl === "string")
18
52
  return validateURL(config.public.siteUrl);
19
- if (event)
20
- return getRequestURL(event).origin;
53
+ const nitroOrigin = getNitroOrigin(event);
54
+ if (nitroOrigin)
55
+ return validateURL(nitroOrigin);
21
56
  if (process.env.VERCEL_URL)
22
57
  return validateURL(`https://${process.env.VERCEL_URL}`);
23
58
  if (process.env.CF_PAGES_URL)
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.13",
4
+ "version": "0.0.2-alpha.15",
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",
@@ -41,7 +50,7 @@
41
50
  "dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground",
42
51
  "dev:docs": "nuxi dev docs",
43
52
  "build:docs": "nuxi build docs",
44
- "release": "bumpp --push",
53
+ "release": "bumpp --push --no-push-all",
45
54
  "lint": "eslint .",
46
55
  "lint:fix": "eslint . --fix",
47
56
  "typecheck": "vue-tsc --noEmit",
@@ -59,16 +68,17 @@
59
68
  }
60
69
  },
61
70
  "dependencies": {
71
+ "@better-auth/cli": "^1.5.0-beta.3",
62
72
  "@nuxt/kit": "^4.2.2",
73
+ "@nuxt/ui": "^4.2.1",
63
74
  "defu": "^6.1.4",
64
75
  "jiti": "^2.4.2",
65
76
  "pathe": "^2.0.3",
66
- "pluralize": "^8.0.0",
67
- "radix3": "^1.1.2"
77
+ "radix3": "^1.1.2",
78
+ "std-env": "^3.10.0"
68
79
  },
69
80
  "devDependencies": {
70
81
  "@antfu/eslint-config": "^4.12.0",
71
- "@better-auth/cli": "^1.4.6",
72
82
  "@libsql/client": "^0.15.15",
73
83
  "@nuxt/devtools": "^3.1.1",
74
84
  "@nuxt/devtools-kit": "^3.1.1",
@@ -78,8 +88,7 @@
78
88
  "@nuxthub/core": "^0.10.3",
79
89
  "@types/better-sqlite3": "^7.6.13",
80
90
  "@types/node": "latest",
81
- "@types/pluralize": "^0.0.33",
82
- "better-auth": "^1.4.7",
91
+ "better-auth": "^1.5.0-beta.3",
83
92
  "better-sqlite3": "^11.9.1",
84
93
  "bumpp": "^10.3.2",
85
94
  "changelogen": "^0.6.2",
@@ -87,6 +96,7 @@
87
96
  "drizzle-kit": "^0.31.8",
88
97
  "drizzle-orm": "^0.38.4",
89
98
  "eslint": "^9.39.1",
99
+ "npm-agentskills": "https://pkg.pr.new/onmax/npm-agentskills@394499e",
90
100
  "nuxt": "^4.2.2",
91
101
  "tinyexec": "^1.0.2",
92
102
  "typescript": "~5.9.3",