@nuxtjs/better-auth 0.3.0 → 0.3.2

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/module.d.mts +2 -2
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +297 -93
  6. package/dist/runtime/app/composables/runWithSessionRefresh.js +2 -1
  7. package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +1 -1
  8. package/dist/runtime/app/composables/useAuthRequestFetch.js +22 -2
  9. package/dist/runtime/app/composables/useSignIn.d.ts +1 -4
  10. package/dist/runtime/app/composables/useUserSession.js +33 -8
  11. package/dist/runtime/app/internal/redirect-helpers.js +22 -1
  12. package/dist/runtime/app/internal/session-fetch.d.ts +1 -0
  13. package/dist/runtime/app/internal/session-fetch.js +22 -8
  14. package/dist/runtime/app/internal/wrap-auth-method.d.ts +3 -2
  15. package/dist/runtime/app/internal/wrap-auth-method.js +9 -7
  16. package/dist/runtime/app/plugins/session.client.js +2 -1
  17. package/dist/runtime/app/plugins/session.server.js +8 -3
  18. package/dist/runtime/composables.d.ts +2 -0
  19. package/dist/runtime/internal/auth-route-rules.d.ts +3 -0
  20. package/dist/runtime/internal/auth-route-rules.js +10 -0
  21. package/dist/runtime/server/internal/cookie-headers.d.ts +3 -0
  22. package/dist/runtime/server/internal/cookie-headers.js +30 -0
  23. package/dist/runtime/server/internal/nitro2.d.ts +4 -2
  24. package/dist/runtime/server/internal/nitro2.js +2 -2
  25. package/dist/runtime/server/internal/nitro3.d.ts +2 -1
  26. package/dist/runtime/server/internal/nitro3.js +5 -32
  27. package/dist/runtime/server/middleware/route-access.js +7 -4
  28. package/dist/runtime/server/utils/auth.js +11 -2
  29. package/dist/runtime/server/utils/session.js +34 -32
  30. package/dist/runtime/test-utils/e2e.d.ts +18 -0
  31. package/dist/runtime/test-utils/e2e.js +48 -0
  32. package/dist/runtime/test-utils/module.d.ts +6 -0
  33. package/dist/runtime/test-utils/module.js +18 -0
  34. package/dist/runtime/test-utils/runtime.d.ts +25 -0
  35. package/dist/runtime/test-utils/runtime.js +56 -0
  36. package/dist/runtime/test-utils/server/handler.d.ts +2 -0
  37. package/dist/runtime/test-utils/server/handler.js +34 -0
  38. package/dist/runtime/test-utils/server/plugin.d.ts +23 -0
  39. package/dist/runtime/test-utils/server/plugin.js +2 -0
  40. package/dist/runtime/types.d.ts +1 -1
  41. package/dist/types.d.mts +2 -8
  42. package/package.json +32 -7
package/dist/module.mjs CHANGED
@@ -1,19 +1,21 @@
1
- import { existsSync, statSync, writeFileSync, readFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
- import { getLayerDirectories, updateTemplates, addServerImports, addServerScanDir, addServerHandler, addImports, addPlugin, addComponentsDir, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
3
+ import { getLayerDirectories, updateTemplates, addServerImports, addServerHandler, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
4
4
  import { consola as consola$1 } from 'consola';
5
+ import { defineDiagnostics, Diagnostic, formatDiagnostic } from 'nostics';
5
6
  import { isAbsolute, join, resolve, relative, dirname } from 'pathe';
7
+ import { createRequire } from 'node:module';
8
+ import { join as join$1 } from 'node:path';
6
9
  import { defu } from 'defu';
7
10
  import { toRouteMatcher, createRouter } from 'radix3';
8
- import { generateDrizzleSchema as generateDrizzleSchema$1 } from 'auth/api';
9
11
  import { randomBytes } from 'node:crypto';
10
12
  import { isCI, isTest } from 'std-env';
11
13
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
12
14
 
13
- const version = "0.3.0";
15
+ const version = "0.3.2";
14
16
 
15
- const CONFIG_EXTENSIONS = [".ts", ".js"];
16
- const CONFIG_EXTENSION_RE$1 = /\.(?:ts|js)$/;
17
+ const CONFIG_EXTENSIONS = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"];
18
+ const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
17
19
  const DEFAULT_CONFIG_FILES = {
18
20
  server: "server/auth.config",
19
21
  client: "app/auth.config"
@@ -27,10 +29,12 @@ const PLUGIN_OPTION_KEY_BY_KIND = {
27
29
  client: "clientPluginSources"
28
30
  };
29
31
  function stripConfigExtension(path) {
30
- return path.replace(CONFIG_EXTENSION_RE$1, "");
32
+ return path.replace(CONFIG_EXTENSION_RE, "");
31
33
  }
32
- function defaultConfigExists(path) {
33
- return CONFIG_EXTENSIONS.some((ext) => existsSync(`${path}${ext}`));
34
+ function resolveAuthConfigFile(path, exists = existsSync) {
35
+ if (CONFIG_EXTENSION_RE.test(path))
36
+ return exists(path) ? path : void 0;
37
+ return CONFIG_EXTENSIONS.map((extension) => `${path}${extension}`).find(exists);
34
38
  }
35
39
  function getLayerDirectoriesWithConfigs(nuxt) {
36
40
  const directories = getLayerDirectories(nuxt);
@@ -47,9 +51,10 @@ function getDefaultConfigPath(nuxt, kind) {
47
51
  function getLayerDefaultConfigPath(nuxt, kind, configExists) {
48
52
  for (const { directory } of getLayerDirectoriesWithConfigs(nuxt)) {
49
53
  const candidate = kind === "server" ? join(directory.server, "auth.config") : join(directory.app, "auth.config");
50
- if (configExists(candidate)) {
54
+ const path = resolveAuthConfigFile(candidate, configExists);
55
+ if (path) {
51
56
  return {
52
- path: candidate,
57
+ path,
53
58
  declaringLayerRoot: directory.root
54
59
  };
55
60
  }
@@ -73,16 +78,18 @@ function getEffectiveModuleConfigFile(nuxt, kind) {
73
78
  return authOptions?.[optionKey] ?? DEFAULT_CONFIG_FILES[kind];
74
79
  }
75
80
  function resolveAuthConfigDescriptor(nuxt, kind, file = getEffectiveModuleConfigFile(nuxt, kind), dependencies = {}) {
76
- const configExists = dependencies.configExists ?? defaultConfigExists;
77
- const configuredFile = stripConfigExtension(file);
81
+ const configExists = dependencies.configExists ?? existsSync;
82
+ const configuredFile = file;
78
83
  if (isAbsolute(configuredFile)) {
79
- const declaringLayerRoot2 = resolveDeclaringLayerRoot(nuxt, kind, configuredFile);
80
- const exists2 = configExists(configuredFile);
84
+ const declaringLayerRoot2 = resolveDeclaringLayerRoot(nuxt, kind, stripConfigExtension(configuredFile));
85
+ const resolvedPath2 = resolveAuthConfigFile(configuredFile, configExists);
86
+ const path2 = resolvedPath2 ?? configuredFile;
87
+ const exists2 = resolvedPath2 !== void 0;
81
88
  return {
82
89
  kind,
83
90
  configuredFile,
84
- file: configuredFile,
85
- path: configuredFile,
91
+ file: path2,
92
+ path: path2,
86
93
  declaringLayerRoot: declaringLayerRoot2,
87
94
  isDefault: false,
88
95
  isExplicit: true,
@@ -95,7 +102,7 @@ function resolveAuthConfigDescriptor(nuxt, kind, file = getEffectiveModuleConfig
95
102
  const discovered = getLayerDefaultConfigPath(nuxt, kind, configExists);
96
103
  const path2 = discovered?.path ?? getDefaultConfigPath(nuxt, kind);
97
104
  const declaringLayerRoot2 = discovered?.declaringLayerRoot ?? project.root;
98
- const exists2 = configExists(path2);
105
+ const exists2 = discovered !== void 0;
99
106
  return {
100
107
  kind,
101
108
  configuredFile,
@@ -108,9 +115,11 @@ function resolveAuthConfigDescriptor(nuxt, kind, file = getEffectiveModuleConfig
108
115
  shouldCreateDefaultFile: !exists2
109
116
  };
110
117
  }
111
- const declaringLayerRoot = resolveDeclaringLayerRoot(nuxt, kind, configuredFile);
112
- const path = join(declaringLayerRoot, configuredFile);
113
- const exists = configExists(path);
118
+ const declaringLayerRoot = resolveDeclaringLayerRoot(nuxt, kind, stripConfigExtension(configuredFile));
119
+ const candidate = join(declaringLayerRoot, configuredFile);
120
+ const resolvedPath = resolveAuthConfigFile(candidate, configExists);
121
+ const path = resolvedPath ?? candidate;
122
+ const exists = resolvedPath !== void 0;
114
123
  return {
115
124
  kind,
116
125
  configuredFile,
@@ -142,16 +151,114 @@ function resolveAuthPluginSources(nuxt) {
142
151
  return resolved;
143
152
  }
144
153
 
154
+ function selectNitro3RouteRulesTarget(nitroTypesDeclaration, resolveH3Rules) {
155
+ if (/\binterface\s+NitroRouteConfig\b/.test(nitroTypesDeclaration)) {
156
+ return {
157
+ moduleName: "nitro/types",
158
+ configInterface: "NitroRouteConfig",
159
+ rulesInterface: "NitroRouteRules"
160
+ };
161
+ }
162
+ return {
163
+ moduleName: resolveH3Rules(),
164
+ configInterface: "RouteRuleConfig",
165
+ rulesInterface: "RouteRules"
166
+ };
167
+ }
168
+ function resolveNitro3RouteRulesTarget(nuxtRootDir) {
169
+ const fallback = {
170
+ moduleName: "nitro/types",
171
+ configInterface: "NitroRouteConfig",
172
+ rulesInterface: "NitroRouteRules"
173
+ };
174
+ try {
175
+ const projectRequire = createRequire(join$1(nuxtRootDir, "package.json"));
176
+ const nuxtRequire = createRequire(projectRequire.resolve("nuxt/package.json"));
177
+ let nitroTypesPath;
178
+ try {
179
+ const integrationRequire = createRequire(nuxtRequire.resolve("@nuxt/nitro-server"));
180
+ nitroTypesPath = integrationRequire.resolve("nitro/types");
181
+ } catch {
182
+ nitroTypesPath = nuxtRequire.resolve("nitro/types");
183
+ }
184
+ const declarationCandidates = [
185
+ nitroTypesPath.replace(/\.mjs$/, ".d.mts"),
186
+ nitroTypesPath.replace(/\.js$/, ".d.ts"),
187
+ nitroTypesPath
188
+ ];
189
+ const declarationPath = declarationCandidates.find((path) => existsSync(path));
190
+ if (!declarationPath)
191
+ return fallback;
192
+ return selectNitro3RouteRulesTarget(
193
+ readFileSync(declarationPath, "utf8"),
194
+ () => createRequire(nitroTypesPath).resolve("h3/rules")
195
+ );
196
+ } catch {
197
+ return fallback;
198
+ }
199
+ }
145
200
  function resolveNitroCompatibilityImports(nuxtVersion) {
146
201
  const major = Number.parseInt(nuxtVersion, 10);
147
202
  const nitroV3 = Number.isFinite(major) && major >= 5;
148
203
  return {
149
204
  h3: nitroV3 ? "nitro/h3" : "h3",
150
- runtime: nitroV3 ? "nitro3" : "nitro2",
151
- types: nitroV3 ? "nitro/types" : "nitropack/types"
205
+ runtime: nitroV3 ? "nitro3" : "nitro2"
152
206
  };
153
207
  }
154
208
 
209
+ function configFilename(file) {
210
+ return /\.[cm]?[jt]s$/.test(file) ? file : `${file}.ts`;
211
+ }
212
+ const diagnostics = defineDiagnostics({
213
+ docsBase: (code) => `https://better-auth.nuxt.dev/errors/${code.toLowerCase().replaceAll("_", "-")}`,
214
+ codes: {
215
+ NUXT_AUTH_MISSING_CONFIG: {
216
+ why: (p) => `Missing ${configFilename(p.file)}`,
217
+ fix: (p) => `Create ${configFilename(p.file)} with export default ${p.factory}(...).`
218
+ },
219
+ NUXT_AUTH_NO_DATABASE_PROVIDER: {
220
+ why: "No database provider is enabled.",
221
+ fix: "Register an enabled provider with the better-auth:database:providers hook."
222
+ },
223
+ NUXT_AUTH_MISSING_HUB_DB: {
224
+ why: "hub:db not found.",
225
+ fix: "Load @nuxthub/core before @nuxtjs/better-auth and configure hub.db."
226
+ },
227
+ NUXT_AUTH_INVALID_PLUGIN_SOURCE: {
228
+ why: (p) => `Modules must register absolute plugin source paths. Received: ${p.source}`,
229
+ fix: "Resolve the plugin source with createResolver(import.meta.url).resolve(...) before registering it."
230
+ },
231
+ NUXT_AUTH_STORAGE_CLIENT_ONLY: {
232
+ why: "hubSecondaryStorage is not available in clientOnly mode.",
233
+ fix: "Either disable clientOnly or remove auth.hubSecondaryStorage."
234
+ },
235
+ NUXT_AUTH_SCHEMA_STORAGE_REQUIRED: {
236
+ why: 'hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth() to omit the session table from the generated schema.',
237
+ fix: "Provide an atomic secondaryStorage in defineServerAuth(), or set auth.hubSecondaryStorage to false to keep database-backed sessions."
238
+ },
239
+ NUXT_AUTH_UNSUPPORTED_DIALECT: {
240
+ why: (p) => `Unsupported database dialect: ${p.dialect}`,
241
+ fix: "Configure hub.db with a supported dialect: sqlite, postgresql, or mysql."
242
+ },
243
+ NUXT_AUTH_INVALID_CONFIG_EXPORT: {
244
+ why: (p) => `${p.configPath} must export default defineServerAuth(...).`,
245
+ fix: "Wrap the server auth options in defineServerAuth(...) and export the result as default."
246
+ },
247
+ NUXT_AUTH_CONFIG_LOAD_FAILED: {
248
+ why: (p) => `Failed to load auth config at ${p.configPath}: ${p.cause instanceof Error ? p.cause.message : String(p.cause)}`,
249
+ fix: "Check the config imports and required environment variables, then restart Nuxt to regenerate the schema."
250
+ },
251
+ NUXT_AUTH_EMPTY_SCHEMA: {
252
+ why: (p) => `Schema generation returned empty result for ${p.dialect}`,
253
+ fix: "Check the Better Auth plugins and database options in defineServerAuth(), then regenerate the schema."
254
+ },
255
+ NUXT_AUTH_SCHEMA_GENERATION_FAILED: {
256
+ why: (p) => `Failed to generate schema: ${p.cause instanceof Error ? p.cause.message : String(p.cause)}`,
257
+ fix: "Check the underlying error, auth configuration, and write access to the Nuxt build directory, then regenerate the schema."
258
+ }
259
+ }
260
+ });
261
+
155
262
  function setupDevTools(nuxt) {
156
263
  const hookable = nuxt;
157
264
  hookable.hook("devtools:customTabs", (tabs) => {
@@ -170,8 +277,11 @@ function setupDevTools(nuxt) {
170
277
 
171
278
  function registerTemplateHmrHook(nuxt) {
172
279
  nuxt.hook("builder:watch", async (_event, relativePath) => {
173
- if (relativePath.includes("auth.config"))
174
- await updateTemplates({ filter: (t) => t.filename.includes("nuxt-better-auth") });
280
+ if (relativePath.includes("auth.config")) {
281
+ await updateTemplates({
282
+ filter: (template) => template.filename.startsWith("better-auth/") || template.filename.startsWith("types/nuxt-better-auth")
283
+ });
284
+ }
175
285
  });
176
286
  }
177
287
  function registerServerRuntime(input) {
@@ -182,7 +292,7 @@ function registerServerRuntime(input) {
182
292
  { name: "serverAuth", from: resolve("./runtime/server/utils/auth") },
183
293
  ...["getRequestSession", "getUserSession", "setRequestSession", "refreshSessionCookieCache", "setSessionCookie", "createSession", "requireUserSession"].map((name) => ({ name, from: resolve("./runtime/server/utils/session") }))
184
294
  ]);
185
- addServerScanDir(resolve("./runtime/server/middleware"));
295
+ addServerHandler({ middleware: true, handler: resolve("./runtime/server/middleware/route-access") });
186
296
  addServerHandler({ route: "/api/auth/**", handler: resolve("./runtime/server/api/auth/[...all]") });
187
297
  }
188
298
  addImports([
@@ -207,9 +317,11 @@ function registerServerRuntime(input) {
207
317
  addPlugin({ src: resolve("./runtime/app/plugins/session.client"), mode: "client" });
208
318
  addComponentsDir({ path: resolve("./runtime/app/components") });
209
319
  }
210
- function registerAuthMiddlewareHook(nuxt, resolve) {
211
- nuxt.hook("app:resolve", (app) => {
212
- app.middleware.push({ name: "auth", path: resolve("./runtime/app/middleware/auth.global"), global: true });
320
+ function registerAuthMiddleware(resolve) {
321
+ addRouteMiddleware({
322
+ name: "auth",
323
+ path: resolve("./runtime/app/middleware/auth.global"),
324
+ global: true
213
325
  });
214
326
  }
215
327
  function registerPrepareTypesHook(input) {
@@ -306,6 +418,7 @@ function dialectToProvider(dialect) {
306
418
  return dialect === "postgresql" ? "pg" : dialect;
307
419
  }
308
420
  async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
421
+ const { generateDrizzleSchema: generateDrizzleSchema2 } = await import('auth/api');
309
422
  const provider = dialectToProvider(dialect);
310
423
  const options = {
311
424
  ...authOptions,
@@ -326,12 +439,12 @@ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
326
439
  adapterConfig: { usePlural: schemaOptions?.usePlural ?? false }
327
440
  }
328
441
  };
329
- const result = await generateDrizzleSchema$1({
442
+ const result = await generateDrizzleSchema2({
330
443
  adapter,
331
444
  options
332
445
  });
333
446
  if (!result.code) {
334
- throw new Error(`Schema generation returned empty result for ${dialect}`);
447
+ throw diagnostics.NUXT_AUTH_EMPTY_SCHEMA({ dialect });
335
448
  }
336
449
  return result.code;
337
450
  }
@@ -343,7 +456,6 @@ function loadLocalEnv(rootDir) {
343
456
  return;
344
457
  process.loadEnvFile(envPath);
345
458
  }
346
- const NO_DEFAULT_EXPORT_MESSAGE = "[@nuxtjs/better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)";
347
459
  const SCHEMA_NOT_REGENERATED_MESSAGE = "The schema was not regenerated and any existing generated schema file was left unchanged.";
348
460
  async function loadUserAuthConfig(configPath, throwOnError = false, alias, runtimeConfig = {}, rootDir) {
349
461
  const { createJiti } = await import('jiti');
@@ -365,17 +477,17 @@ async function loadUserAuthConfig(configPath, throwOnError = false, alias, runti
365
477
  if (typeof configFn === "function") {
366
478
  return configFn({ runtimeConfig, db: null });
367
479
  }
368
- if (throwOnError) {
369
- consola$1.warn(NO_DEFAULT_EXPORT_MESSAGE);
370
- throw new Error("auth.config.ts must export default defineServerAuth(...)");
371
- }
372
- consola$1.error(`${NO_DEFAULT_EXPORT_MESSAGE}. ${SCHEMA_NOT_REGENERATED_MESSAGE}`);
373
- return null;
480
+ throw diagnostics.NUXT_AUTH_INVALID_CONFIG_EXPORT({ configPath });
374
481
  } catch (error) {
375
- if (throwOnError) {
376
- throw new Error(`Failed to load auth config: ${error instanceof Error ? error.message : error}`);
377
- }
378
- consola$1.error(`[@nuxtjs/better-auth] Failed to load auth config for schema generation. ${SCHEMA_NOT_REGENERATED_MESSAGE}`, error);
482
+ const diagnostic = error instanceof Diagnostic ? error : diagnostics.NUXT_AUTH_CONFIG_LOAD_FAILED({ configPath, cause: error });
483
+ if (throwOnError)
484
+ throw diagnostic;
485
+ const message = `${formatDiagnostic(diagnostic)}
486
+ ${SCHEMA_NOT_REGENERATED_MESSAGE}`;
487
+ if (diagnostic.cause === void 0)
488
+ consola$1.error(message);
489
+ else
490
+ consola$1.error(message, diagnostic.cause);
379
491
  return null;
380
492
  } finally {
381
493
  const sharedDefineServerAuth = schemaGlobals.__nuxtBetterAuthDefineServerAuth;
@@ -407,7 +519,6 @@ function getHubCasing(hub) {
407
519
  }
408
520
 
409
521
  const NODE_MODULES_SEGMENT_RE = /[\\/]/;
410
- const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
411
522
  function resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, isProduction) {
412
523
  if (hubSecondaryStorage === true)
413
524
  return { inject: false };
@@ -415,10 +526,10 @@ function resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSeco
415
526
  return { inject: false };
416
527
  if (userHasSecondaryStorage)
417
528
  return { inject: true };
418
- const message = '[nuxt-better-auth] hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth() to omit the session table from the generated schema.';
529
+ const diagnostic = diagnostics.NUXT_AUTH_SCHEMA_STORAGE_REQUIRED();
419
530
  if (isProduction)
420
- return { inject: false, error: message };
421
- return { inject: false, warn: message };
531
+ return { inject: false, error: diagnostic };
532
+ return { inject: false, warn: diagnostic };
422
533
  }
423
534
  function isInsideNodeModules(path) {
424
535
  return path.split(NODE_MODULES_SEGMENT_RE).includes("node_modules");
@@ -448,7 +559,7 @@ function registerNuxtHubSchemaHook(nuxt, finishSetup) {
448
559
  }
449
560
  async function loadAuthOptions(context) {
450
561
  const isProduction = !context.nuxt.options.dev;
451
- const configFile = CONFIG_EXTENSION_RE.test(context.serverConfigPath) ? context.serverConfigPath : `${context.serverConfigPath}.ts`;
562
+ const configFile = resolveAuthConfigFile(context.serverConfigPath) ?? context.serverConfigPath;
452
563
  const alias = Object.fromEntries(
453
564
  Object.entries(context.nuxt.options.alias).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
454
565
  );
@@ -464,7 +575,7 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
464
575
  const hub = nuxt.options.hub;
465
576
  const dialect = getHubDialect(hub);
466
577
  if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
467
- consola.warn(`Unsupported database dialect: ${dialect}`);
578
+ consola.warn(formatDiagnostic(diagnostics.NUXT_AUTH_UNSUPPORTED_DIALECT({ dialect })));
468
579
  return;
469
580
  }
470
581
  const context = { nuxt, serverConfigPath };
@@ -476,9 +587,9 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
476
587
  const userHasSecondaryStorage = userConfig.secondaryStorage != null;
477
588
  const secondaryStorageResolution = resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, !nuxt.options.dev);
478
589
  if (secondaryStorageResolution.error)
479
- throw new Error(secondaryStorageResolution.error);
590
+ throw secondaryStorageResolution.error;
480
591
  if (secondaryStorageResolution.warn)
481
- consola.warn(secondaryStorageResolution.warn);
592
+ consola.warn(formatDiagnostic(secondaryStorageResolution.warn));
482
593
  const authOptions = {
483
594
  ...userConfig,
484
595
  plugins,
@@ -511,11 +622,7 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
511
622
  addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
512
623
  consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
513
624
  } catch (error) {
514
- const isProduction = !nuxt.options.dev;
515
- if (isProduction)
516
- throw error;
517
- consola.error("Failed to generate schema:", error);
518
- throw error;
625
+ throw error instanceof Diagnostic ? error : diagnostics.NUXT_AUTH_SCHEMA_GENERATION_FAILED({ cause: error });
519
626
  }
520
627
  }
521
628
 
@@ -601,7 +708,7 @@ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
601
708
  function resolveDatabaseProvider(input) {
602
709
  const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
603
710
  if (!enabledProviders.length) {
604
- throw new Error("[nuxt-better-auth] No database provider is enabled. Register one with the better-auth:database:providers hook.");
711
+ throw diagnostics.NUXT_AUTH_NO_DATABASE_PROVIDER();
605
712
  }
606
713
  enabledProviders.sort((a, b) => (b[1].priority ?? 0) - (a[1].priority ?? 0));
607
714
  const [id, definition] = enabledProviders[0];
@@ -620,7 +727,7 @@ function resolveSecondaryStorage(input) {
620
727
  }
621
728
  const secondaryStorageEnabled = opt === "custom";
622
729
  if (secondaryStorageEnabled && clientOnly) {
623
- throw new Error("[nuxt-better-auth] hubSecondaryStorage is not available in clientOnly mode. Either disable clientOnly or remove auth.hubSecondaryStorage.");
730
+ throw diagnostics.NUXT_AUTH_STORAGE_CLIENT_ONLY();
624
731
  }
625
732
  return { hubSecondaryStorage: opt, secondaryStorageEnabled };
626
733
  }
@@ -832,9 +939,9 @@ function buildAuthRouteRulesCode(authRouteRules) {
832
939
 
833
940
  function assertConfigPresence(configs, clientOnly) {
834
941
  if (!clientOnly && !configs.server.exists)
835
- throw new Error(`[nuxt-better-auth] Missing ${configs.server.file}.ts - export default defineServerAuth(...)`);
942
+ throw diagnostics.NUXT_AUTH_MISSING_CONFIG({ file: configs.server.file, factory: "defineServerAuth" });
836
943
  if (!configs.client.exists)
837
- throw new Error(`[nuxt-better-auth] Missing ${configs.client.file}.ts - export default defineClientAuth(...)`);
944
+ throw diagnostics.NUXT_AUTH_MISSING_CONFIG({ file: configs.client.file, factory: "defineClientAuth" });
838
945
  }
839
946
  function createDefaultDatabaseProviders(buildContext) {
840
947
  return {
@@ -856,7 +963,7 @@ function createDefaultDatabaseProviders(buildContext) {
856
963
  };
857
964
  }
858
965
  function collectAuthRouteRules(nuxt) {
859
- const runtimeRouteRulesSource = nuxt.options.nitro?.routeRules || nuxt.options.routeRules || {};
966
+ const runtimeRouteRulesSource = getRuntimeRouteRules(nuxt);
860
967
  return Object.fromEntries(
861
968
  Object.entries(runtimeRouteRulesSource).flatMap(([path, rule]) => {
862
969
  if (!rule || typeof rule !== "object" || !("auth" in rule))
@@ -865,6 +972,63 @@ function collectAuthRouteRules(nuxt) {
865
972
  })
866
973
  );
867
974
  }
975
+ const authIncompatibleRouteRuleKeys = ["cache", "swr", "isr", "static", "prerender", "proxy"];
976
+ function getRuntimeRouteRules(nuxt) {
977
+ return nuxt.options.nitro?.routeRules || nuxt.options.routeRules || {};
978
+ }
979
+ function registerAuthRouteRulesValidation(nuxt) {
980
+ nuxt.hook("nitro:init", (nitro) => {
981
+ assertSafeAuthRouteRules(nitro.options.routeRules);
982
+ });
983
+ }
984
+ function assertSafeAuthRouteRules(routeRules) {
985
+ if (!Object.keys(routeRules).length)
986
+ return;
987
+ const matcher = toRouteMatcher(createRouter({ routes: routeRules }));
988
+ const paths = new Set(Object.keys(routeRules));
989
+ const patterns = [...collectRouteRulePatterns(matcher.ctx.table)];
990
+ for (const path of collectRouteRulePaths(patterns, ""))
991
+ paths.add(path);
992
+ const conflicts = [...paths].flatMap((path) => {
993
+ const matches = matcher.matchAll(path);
994
+ const effectiveRule = defu({}, ...matches.reverse());
995
+ if (effectiveRule.auth === void 0 || effectiveRule.auth === false)
996
+ return [];
997
+ const incompatibleKeys = authIncompatibleRouteRuleKeys.filter((key) => {
998
+ const value = effectiveRule[key];
999
+ return value !== void 0 && value !== false && value !== 0;
1000
+ });
1001
+ return incompatibleKeys.length ? [{ path, incompatibleKeys }] : [];
1002
+ });
1003
+ if (!conflicts.length)
1004
+ return;
1005
+ const details = conflicts.map(({ path, incompatibleKeys }) => `${path} (${incompatibleKeys.join(", ")})`).join(", ");
1006
+ throw new Error(
1007
+ `[nuxt-better-auth] Auth route rules cannot be combined with cache, swr, isr, static, prerender, or proxy rules. These rules can run before authentication or share user-specific responses. Conflicts: ${details}`
1008
+ );
1009
+ }
1010
+ function* collectRouteRulePatterns(table, prefix = []) {
1011
+ const segments = (path) => path && path !== "/" ? path.slice(1).split("/") : [];
1012
+ for (const path of [...table.static.keys(), ...table.wildcard.keys()])
1013
+ yield [...prefix, ...segments(path)];
1014
+ for (const [path, child] of table.dynamic)
1015
+ yield* collectRouteRulePatterns(child, [...prefix, ...segments(path), null]);
1016
+ }
1017
+ function* collectRouteRulePaths(patterns, prefix) {
1018
+ yield prefix || "/";
1019
+ const literals = new Set(patterns.flatMap((pattern) => typeof pattern[0] === "string" ? [pattern[0]] : []));
1020
+ let other = "_";
1021
+ while (literals.has(other))
1022
+ other += "_";
1023
+ for (const segment of [...literals, other]) {
1024
+ const path = `${prefix}/${segment}`;
1025
+ const next = patterns.filter((pattern) => pattern[0] === null || pattern[0] === segment).map((pattern) => pattern.slice(1));
1026
+ if (next.length)
1027
+ yield* collectRouteRulePaths(next, path);
1028
+ else
1029
+ yield path;
1030
+ }
1031
+ }
868
1032
  async function resolveAuthModuleSetup(input, dependencies = {}) {
869
1033
  const { nuxt, options, runtimeTypesAugmentPath, consola } = input;
870
1034
  const hasNuxtModuleFn = dependencies.hasNuxtModule ?? hasNuxtModule;
@@ -911,7 +1075,7 @@ async function resolveAuthModuleSetup(input, dependencies = {}) {
911
1075
  providerId = resolvedProvider.id;
912
1076
  providerDefinition = resolvedProvider.definition;
913
1077
  }
914
- const runtime = setupRuntimeConfig({
1078
+ setupRuntimeConfig({
915
1079
  nuxt,
916
1080
  options,
917
1081
  clientOnly,
@@ -920,39 +1084,29 @@ async function resolveAuthModuleSetup(input, dependencies = {}) {
920
1084
  });
921
1085
  const hasHubDb = providerId === "nuxthub";
922
1086
  if (hasHubDb && !nuxt.options.alias["hub:db"]) {
923
- throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
1087
+ throw diagnostics.NUXT_AUTH_MISSING_HUB_DB();
924
1088
  }
925
1089
  return {
926
1090
  clientOnly,
927
1091
  configs,
928
1092
  aliases,
929
1093
  pluginSources,
930
- hub: {
931
- hasNuxtHub,
932
- options: hub,
933
- hasHubDbAvailable
934
- },
935
1094
  database: {
936
- providerId,
937
1095
  hasHubDb,
938
1096
  providerDefinition,
939
1097
  buildContext: clientOnly ? void 0 : { hubDialect, usePlural, camelCase }
940
1098
  },
941
- runtime,
942
1099
  prepareTypes: clientOnly ? void 0 : {
943
1100
  serverDir: dirname(configs.server.path),
944
1101
  hasHubDb
945
1102
  },
946
1103
  serverTypes: clientOnly ? void 0 : {
947
- serverConfigPath: configs.server.path,
948
1104
  hasHubDb
949
1105
  },
950
1106
  sharedTypes: {
951
- runtimeTypesAugmentPath,
952
- clientConfigPath: configs.client.path
1107
+ runtimeTypesAugmentPath
953
1108
  },
954
1109
  schemaGeneration: hasHubDb ? {
955
- serverConfigPath: configs.server.path,
956
1110
  hubSecondaryStorage: options.hubSecondaryStorage ?? false,
957
1111
  externalizeNuxtHubDatabase: true
958
1112
  } : void 0
@@ -975,8 +1129,22 @@ function buildNuxtFetchFallback(name) {
975
1129
  `).join("");
976
1130
  }
977
1131
  function registerServerTypeTemplates(input) {
978
- const { serverConfigPath, hasHubDb, runtimeTypesPath, sharedServerConfigSafe, h3TypesPath, nitroTypesPath } = input;
979
- const routeRuleAugmentations = (nitroTypesPath === "nitro/types" ? [nitroTypesPath] : ["nitropack", nitroTypesPath]).map((moduleName) => `declare module '${moduleName}' {
1132
+ const { serverConfigPath, hasHubDb, runtimeTypesPath, sharedServerConfigSafe, h3TypesPath, nitro3RouteRulesTarget } = input;
1133
+ const fetchRoutingTypeDeclarations = h3TypesPath === "nitro/h3" ? `type NitroFetchRequest = string & {}
1134
+ type AvailableRouterMethod<_ReqT extends NitroFetchRequest> = 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace'` : `import type { AvailableRouterMethod, NitroFetchRequest } from 'nitropack/types'`;
1135
+ const resolvedNitro3RouteRulesTarget = nitro3RouteRulesTarget ?? {
1136
+ moduleName: "nitro/types",
1137
+ configInterface: "NitroRouteConfig",
1138
+ rulesInterface: "NitroRouteRules"
1139
+ };
1140
+ const routeRuleAugmentations = h3TypesPath === "nitro/h3" ? `declare module ${JSON.stringify(resolvedNitro3RouteRulesTarget.moduleName)} {
1141
+ interface ${resolvedNitro3RouteRulesTarget.configInterface} {
1142
+ auth?: import('${runtimeTypesPath}').AuthMeta
1143
+ }
1144
+ interface ${resolvedNitro3RouteRulesTarget.rulesInterface} {
1145
+ auth?: import('${runtimeTypesPath}').AuthMeta
1146
+ }
1147
+ }` : ["nitropack", "nitropack/types"].map((moduleName) => `declare module '${moduleName}' {
980
1148
  interface NitroRouteRules {
981
1149
  auth?: import('${runtimeTypesPath}').AuthMeta
982
1150
  }
@@ -1112,11 +1280,38 @@ declare module '#nuxt-better-auth' {
1112
1280
  import type createServerAuth from '${serverConfigPath}'
1113
1281
  import type { BetterAuthOptions } from 'better-auth'
1114
1282
  import type { getEndpoints } from 'better-auth/api'
1115
- import type { AvailableRouterMethod, NitroFetchRequest, Serialize, Simplify } from '${nitroTypesPath}'
1116
1283
  import type { useFetch as _NuxtUseFetch } from '#app/composables/fetch'
1284
+ ${fetchRoutingTypeDeclarations}
1117
1285
 
1118
1286
  type _NuxtFetchError = NonNullable<ReturnType<typeof _NuxtUseFetch<void>>['error']['value']>
1119
1287
 
1288
+ type _JsonPrimitive = string | number | boolean | null
1289
+ type _NonJsonPrimitive = undefined | Function | symbol
1290
+ type _IsAny<T> = 0 extends 1 & T ? true : false
1291
+ type _FilterKeys<TObj extends object, TFilter> = {
1292
+ [TKey in keyof TObj]: TObj[TKey] extends TFilter ? TKey : never
1293
+ }[keyof TObj]
1294
+ type _Serialize<T> = _IsAny<T> extends true
1295
+ ? any
1296
+ : T extends _JsonPrimitive | undefined
1297
+ ? T
1298
+ : T extends Map<any, any> | Set<any>
1299
+ ? Record<string, never>
1300
+ : T extends _NonJsonPrimitive
1301
+ ? never
1302
+ : T extends { toJSON: () => infer U }
1303
+ ? U
1304
+ : T extends []
1305
+ ? []
1306
+ : T extends [unknown, ...unknown[]]
1307
+ ? { [K in keyof T]: T[K] extends _NonJsonPrimitive ? null : _Serialize<T[K]> }
1308
+ : T extends ReadonlyArray<infer U>
1309
+ ? (U extends _NonJsonPrimitive ? null : _Serialize<U>)[]
1310
+ : T extends object
1311
+ ? { [K in keyof Omit<T, _FilterKeys<T, _NonJsonPrimitive>>]: _Serialize<T[K]> }
1312
+ : never
1313
+ type _Simplify<T> = T extends any[] | Date ? T : { [K in keyof T]: _Simplify<T[K]> }
1314
+
1120
1315
  type _RawConfig = ReturnType<typeof createServerAuth>
1121
1316
  type _RawPlugins = _RawConfig extends { plugins: infer P } ? P : _RawConfig extends { plugins?: infer P } ? P : []
1122
1317
  type _Config = Omit<BetterAuthOptions, 'plugins'> & Omit<_RawConfig, 'plugins'> & {
@@ -1136,7 +1331,7 @@ type _RoutePathFromEndpoint<E> = E extends { path: infer P extends string }
1136
1331
  ? never
1137
1332
  : \`/api/auth\${P}\`
1138
1333
  : never
1139
- type _RouteResponseFromEndpoint<E> = E extends (...args: any[]) => Promise<infer R> ? Simplify<Serialize<Awaited<R>>> : never
1334
+ type _RouteResponseFromEndpoint<E> = E extends (...args: any[]) => Promise<infer R> ? _Simplify<_Serialize<Awaited<R>>> : never
1140
1335
  type _RouteDefaultResponse<E> = never
1141
1336
  type _UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void ? I : never
1142
1337
 
@@ -1253,10 +1448,20 @@ export {}
1253
1448
  ${routeRuleAugmentations}
1254
1449
  export {}
1255
1450
  `
1256
- }, { nitro: true, node: true });
1451
+ }, { nuxt: h3TypesPath === "nitro/h3", nitro: true, node: true });
1257
1452
  }
1258
1453
  function registerSharedTypeTemplates(input) {
1259
1454
  const nitroV3 = input.h3TypesPath === "nitro/h3";
1455
+ const clientOnlyAuthApiTypes = input.clientOnly ? `
1456
+ export type AuthApiInternalRoutes = Record<string, Record<string, unknown>>
1457
+ export type AuthApiEndpointPatternPath = string
1458
+ export type AuthApiEndpointPath = string
1459
+ export type AuthApiEndpointMethod<Path extends AuthApiEndpointPath> = string
1460
+ export type AuthApiEndpointResponse<
1461
+ Path extends AuthApiEndpointPath,
1462
+ Method extends AuthApiEndpointMethod<Path> = AuthApiEndpointMethod<Path>,
1463
+ > = unknown
1464
+ ` : "";
1260
1465
  addTypeTemplate({
1261
1466
  filename: "types/nuxt-better-auth.d.ts",
1262
1467
  getContents: () => `
@@ -1293,7 +1498,8 @@ declare module '#nuxt-better-auth' {
1293
1498
  }
1294
1499
 
1295
1500
  export interface AuthSocialProviderRegistry {}
1296
- export type AuthSocialProviderId = AuthSocialProviderRegistry extends { ids: infer T } ? Extract<T, string> : never
1501
+ export type AuthSocialProviderId = AuthSocialProviderRegistry extends { ids: infer T } ? Extract<T, string> : string
1502
+ ${clientOnlyAuthApiTypes}
1297
1503
 
1298
1504
  export interface AuthUserUpdateInput {
1299
1505
  name?: string
@@ -1360,15 +1566,7 @@ const dbIdentifierRE = /\bdb\b/;
1360
1566
  const sessionHookAfterIdentifierRE = /\bsessionHookAfter\b/;
1361
1567
  const nuxtHubDbImportRE = /@nuxthub\/db/;
1362
1568
  function isServerConfigSharedTypeSafe(serverConfigPath) {
1363
- const resolvedPath = [
1364
- serverConfigPath,
1365
- `${serverConfigPath}.ts`,
1366
- `${serverConfigPath}.mts`,
1367
- `${serverConfigPath}.cts`,
1368
- `${serverConfigPath}.js`,
1369
- `${serverConfigPath}.mjs`,
1370
- `${serverConfigPath}.cjs`
1371
- ].find((path) => existsSync(path));
1569
+ const resolvedPath = resolveAuthConfigFile(serverConfigPath);
1372
1570
  if (!resolvedPath)
1373
1571
  return false;
1374
1572
  const contents = readFileSync(resolvedPath, "utf8");
@@ -1433,7 +1631,7 @@ const module$1 = defineNuxtModule({
1433
1631
  await nuxt.callHook("better-auth:plugins:extend", registeredPluginSources);
1434
1632
  for (const source of [...registeredPluginSources.server || [], ...registeredPluginSources.client || []]) {
1435
1633
  if (!isAbsolute(source))
1436
- throw new Error(`[nuxt-better-auth] Modules must register absolute plugin source paths. Received: ${source}`);
1634
+ throw diagnostics.NUXT_AUTH_INVALID_PLUGIN_SOURCE({ source });
1437
1635
  }
1438
1636
  const setup = await resolveAuthModuleSetup({
1439
1637
  nuxt,
@@ -1495,6 +1693,7 @@ const module$1 = defineNuxtModule({
1495
1693
  };
1496
1694
  await setup.database.providerDefinition.setup?.(setupCtx);
1497
1695
  }
1696
+ registerAuthRouteRulesValidation(nuxt);
1498
1697
  const authRouteRulesTemplate = addTemplate({
1499
1698
  filename: "better-auth/route-rules.mjs",
1500
1699
  getContents: () => buildAuthRouteRulesCode(collectAuthRouteRules(nuxt)),
@@ -1508,7 +1707,7 @@ const module$1 = defineNuxtModule({
1508
1707
  runtimeTypesPath: resolver.resolve("./runtime/types"),
1509
1708
  sharedServerConfigSafe: [setup.configs.server.path, ...setup.pluginSources.server].every(isServerConfigSharedTypeSafe),
1510
1709
  h3TypesPath: nitroImports.h3,
1511
- nitroTypesPath: nitroImports.types
1710
+ nitro3RouteRulesTarget: nitroImports.runtime === "nitro3" ? resolveNitro3RouteRulesTarget(nuxt.options.rootDir) : void 0
1512
1711
  });
1513
1712
  }
1514
1713
  if (setup.schemaGeneration) {
@@ -1526,18 +1725,23 @@ const module$1 = defineNuxtModule({
1526
1725
  runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
1527
1726
  runtimeTypesPath: resolver.resolve("./runtime/types"),
1528
1727
  clientConfigPath,
1529
- h3TypesPath: nitroImports.h3
1728
+ h3TypesPath: nitroImports.h3,
1729
+ clientOnly: setup.clientOnly
1530
1730
  });
1531
1731
  registerTemplateHmrHook(nuxt);
1532
1732
  registerServerRuntime({ clientOnly: setup.clientOnly, resolve: resolver.resolve });
1533
- registerAuthMiddlewareHook(nuxt, resolver.resolve);
1733
+ registerAuthMiddleware(resolver.resolve);
1534
1734
  await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
1535
1735
  registerRouteRulesMetaHook(nuxt);
1536
1736
  return Boolean(setup.schemaGeneration);
1537
1737
  };
1538
1738
  let setupPromise;
1539
1739
  const finishSetupOnce = () => {
1540
- setupPromise ||= finishSetup();
1740
+ setupPromise ||= finishSetup().catch((error) => {
1741
+ if (error instanceof Diagnostic)
1742
+ throw new Error(formatDiagnostic(error), { cause: error });
1743
+ throw error;
1744
+ });
1541
1745
  return setupPromise;
1542
1746
  };
1543
1747
  registerNuxtHubSchemaHook(nuxt, finishSetupOnce);