nuxt-convex-module 0.10.0 → 0.12.0

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 (71) hide show
  1. package/README.md +26 -47
  2. package/dist/chunks/index.mjs +72 -18
  3. package/dist/devtools-client/_nuxt/AqohFEu1.js +1 -0
  4. package/dist/devtools-client/_nuxt/BPtiIkWa.js +3 -0
  5. package/dist/devtools-client/_nuxt/DGCFzitC.js +7 -0
  6. package/dist/devtools-client/_nuxt/entry.egvhSnkx.css +1 -0
  7. package/dist/devtools-client/index.html +1 -1
  8. package/dist/module.d.mts +18 -18
  9. package/dist/module.json +2 -2
  10. package/dist/module.mjs +101 -66
  11. package/dist/runtime/auth0/vue/index.js +1 -1
  12. package/dist/runtime/better-auth/nuxt/middleware.js +2 -13
  13. package/dist/runtime/better-auth/nuxt/proxy.d.ts +12 -12
  14. package/dist/runtime/better-auth/nuxt/proxy.js +8 -6
  15. package/dist/runtime/better-auth/nuxt/server-guard.d.ts +12 -0
  16. package/dist/runtime/better-auth/nuxt/server-guard.js +13 -0
  17. package/dist/runtime/better-auth/nuxt/server.d.ts +22 -10
  18. package/dist/runtime/better-auth/nuxt/server.js +11 -4
  19. package/dist/runtime/better-auth/vue/auth-boundary.js +2 -2
  20. package/dist/runtime/better-auth/vue/client.d.ts +5 -357
  21. package/dist/runtime/better-auth/vue/plugin.server.js +4 -5
  22. package/dist/runtime/better-auth/vue/use-better-auth.d.ts +4 -65
  23. package/dist/runtime/clerk/vue/index.js +1 -1
  24. package/dist/runtime/nuxt/app.d.ts +0 -3
  25. package/dist/runtime/nuxt/composables/use-async-paginated-query.js +12 -12
  26. package/dist/runtime/nuxt/composables/use-async-query.d.ts +2 -2
  27. package/dist/runtime/nuxt/config.js +1 -1
  28. package/dist/runtime/nuxt/index.d.ts +6 -6
  29. package/dist/runtime/nuxt/security.d.ts +2 -1
  30. package/dist/runtime/nuxt/security.js +2 -2
  31. package/dist/runtime/polar/vue/components.d.ts +14 -16
  32. package/dist/runtime/polar/vue/components.js +10 -15
  33. package/dist/runtime/vue/auth/helpers.js +4 -4
  34. package/dist/runtime/vue/client.d.ts +6 -6
  35. package/dist/runtime/vue/components/convex-image.js +1 -1
  36. package/dist/runtime/vue/composables/use-action.d.ts +3 -3
  37. package/dist/runtime/vue/composables/use-mutation.d.ts +4 -4
  38. package/dist/runtime/vue/composables/use-queries.d.ts +14 -7
  39. package/dist/runtime/vue/composables/use-query.d.ts +5 -5
  40. package/dist/runtime/vue/hydration.d.ts +3 -3
  41. package/dist/runtime/vue/index.d.ts +1 -2
  42. package/dist/runtime/vue/index.js +0 -6
  43. package/dist/runtime/vue/queries-observer.d.ts +9 -9
  44. package/package.json +43 -41
  45. package/dist/devtools-client/200.html +0 -1
  46. package/dist/devtools-client/404.html +0 -1
  47. package/dist/devtools-client/_nuxt/71bSMC-_.js +0 -2
  48. package/dist/devtools-client/_nuxt/BAaUCl_m.js +0 -1
  49. package/dist/devtools-client/_nuxt/BhmjAr2Q.js +0 -1
  50. package/dist/devtools-client/_nuxt/BmBQNDID.js +0 -1
  51. package/dist/devtools-client/_nuxt/C6YdTgGl.js +0 -2
  52. package/dist/devtools-client/_nuxt/CKakiU_h.js +0 -1
  53. package/dist/devtools-client/_nuxt/Cm7YqZQ-.js +0 -1
  54. package/dist/devtools-client/_nuxt/DQmVr-EY.js +0 -1
  55. package/dist/devtools-client/_nuxt/D_Rij7MD.js +0 -1
  56. package/dist/devtools-client/_nuxt/EZ4-8Xzm.js +0 -3
  57. package/dist/devtools-client/_nuxt/Kba0XpjH.js +0 -1
  58. package/dist/devtools-client/_nuxt/OVE7s6ty.js +0 -1
  59. package/dist/devtools-client/_nuxt/_Lf3mfPR.js +0 -1
  60. package/dist/devtools-client/_nuxt/builds/latest.json +0 -1
  61. package/dist/devtools-client/_nuxt/builds/meta/1734dcfa-caf6-447b-a68b-4dd3cf138875.json +0 -1
  62. package/dist/devtools-client/_nuxt/entry.HF8C6P11.css +0 -1
  63. package/dist/devtools-client/_nuxt/error-404.CLlwiMp-.css +0 -1
  64. package/dist/devtools-client/_nuxt/error-500.Bxr6fpDU.css +0 -1
  65. package/dist/devtools-client/_nuxt/queries.DNBUjWk2.css +0 -1
  66. package/dist/devtools-client/_nuxt/vZZlEX5A.js +0 -1
  67. package/dist/devtools-client/auth/index.html +0 -1
  68. package/dist/devtools-client/logs/index.html +0 -1
  69. package/dist/devtools-client/queries/index.html +0 -1
  70. package/dist/runtime/vue/provide.d.ts +0 -39
  71. package/dist/runtime/vue/provide.js +0 -15
package/dist/module.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
- /** The `convex.autoImports` option: every name, only the `Convex`-marked ones, or none. */
3
+ /** The `convex.autoImports` option: every name, every name prefixed with `Convex`, or none. */
4
4
  type AutoImports = boolean | 'prefixed';
5
5
 
6
6
  /**
@@ -22,6 +22,12 @@ interface BetterAuthModuleOptions {
22
22
  * @example './app/convex-auth-client'
23
23
  */
24
24
  authClient?: string;
25
+ /**
26
+ * Route the same-origin proxy to Better Auth is mounted at. Defaults to
27
+ * `/api/auth`, Better Auth's own default `basePath`; moving it needs an
28
+ * `authClient` created with the same `basePath`.
29
+ */
30
+ authRoute?: string;
25
31
  /**
26
32
  * Restrict cross-domain one-time-token (`?ott=`) sign-in completion to one
27
33
  * app route, e.g. `'/auth/callback'`. Only meaningful when your auth client
@@ -95,14 +101,13 @@ interface ModuleOptions {
95
101
  * Which names are auto-imported. `true` registers every composable, component
96
102
  * and Nitro helper under its upstream name (`useQuery`, `<Authenticated>`,
97
103
  * `fetchQuery`) and, where one exists, its prefixed twin (`useConvexQuery`).
98
- * `'prefixed'` registers only names carrying `Convex` (`useConvexQuery`,
99
- * `<ConvexImage>`, `provideConvexAuth`, `convexAuth`, …) so a bare name never
100
- * collides with another module's; the rest stay importable from the subpath
101
- * exports. `false` auto-imports nothing.
104
+ * `'prefixed'` registers every one of them with `Convex` in its name, as the
105
+ * Nuxt module author guide asks (`useConvexQuery`, `<ConvexAuthenticated>`,
106
+ * `fetchConvexQuery`, `useConvexBetterAuth`, …), so a bare name never
107
+ * collides with another module's; the upstream names stay importable from
108
+ * the subpath exports. `false` auto-imports nothing.
102
109
  */
103
110
  autoImports?: AutoImports;
104
- /** Route the Better Auth same-origin proxy is mounted at. Defaults to `/api/auth`. */
105
- authRoute?: string;
106
111
  /**
107
112
  * Convex panel in Nuxt DevTools (dev only): connection state, live query
108
113
  * subscriptions, auth state, and client logs. Enabled by default whenever
@@ -112,21 +117,16 @@ interface ModuleOptions {
112
117
  /**
113
118
  * Run Convex beside Nuxt with one command. On the first `nuxt dev` (or
114
119
  * `nuxt prepare`) in an app whose `dev` script is still the plain
115
- * `nuxt dev`, the module rewrites it to `convex dev --start 'nuxt dev'`:
116
- * the Convex CLI starts the dev deployment, starts Nuxt next to it, and
117
- * hands it `CONVEX_URL` / `CONVEX_SITE_URL` in the environment — no `.env`
118
- * needed for local development. A `dev` script you have already changed,
119
- * or one that runs `convex dev` somewhere, is left alone. Set `false` to
120
- * never touch `package.json`.
120
+ * `nuxt dev`, the module rewrites it to `convex dev --start "nuxt dev"`:
121
+ * the Convex CLI starts the dev deployment, writes its URL to `.env.local`
122
+ * and starts Nuxt next to it, and the module reads that file in
123
+ * development — no `.env` needed for local development. A `dev` script you
124
+ * have already changed, or one that runs `convex dev` somewhere, is left
125
+ * alone. Set `false` to never touch `package.json`.
121
126
  */
122
127
  devScript?: boolean;
123
128
  }
124
129
  declare module '@nuxt/schema' {
125
- interface RuntimeConfig {
126
- convex: {
127
- siteUrl: string;
128
- };
129
- }
130
130
  interface PublicRuntimeConfig {
131
131
  convex: {
132
132
  url: string;
package/dist/module.json CHANGED
@@ -3,9 +3,9 @@
3
3
  "configKey": "convex",
4
4
  "docs": "https://nuxt-convex-module.dev",
5
5
  "compatibility": {
6
- "nuxt": ">=4.1.0"
6
+ "nuxt": ">=4.6.0"
7
7
  },
8
- "version": "0.10.0",
8
+ "version": "0.12.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,7 +1,9 @@
1
- import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addServerPlugin, addPluginTemplate, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
2
- import { join, isAbsolute } from 'node:path';
1
+ import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addNitroPlugin, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
2
+ import { dirname, join, isAbsolute, basename } from 'node:path';
3
+ import { detectAgent } from 'std-env';
3
4
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
4
- import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { parseEnv } from 'node:util';
5
7
 
6
8
  function resolveIntegrationState(explicit, installed, declared = installed) {
7
9
  if (explicit === false) return { enabled: false, missingPackage: false };
@@ -50,11 +52,11 @@ function validateModuleOptions(input) {
50
52
  `\`convex.siteUrl\` ("${input.siteUrl}") does not look like a valid http(s) URL.`
51
53
  );
52
54
  }
53
- const authRoute = normalizeRoutePath(input.authRoute, "convex.authRoute", warnings);
55
+ const authRoute = normalizeRoutePath(input.authRoute, "convex.betterAuth.authRoute", warnings);
54
56
  const loginPath = normalizeRoutePath(input.loginPath ?? "/login", "convex.betterAuth.loginPath", warnings);
55
57
  if (authRoute !== "/api/auth" && !input.authClient) {
56
58
  errors.push(
57
- `\`convex.authRoute\` is "${authRoute}" but the bundled Better Auth client still calls /api/auth, so every auth request would 404. Keep the default route, or set \`convex.betterAuth.authClient\` to a client created with \`basePath: "${authRoute}"\`.`
59
+ `\`convex.betterAuth.authRoute\` is "${authRoute}" but the bundled Better Auth client still calls /api/auth, so every auth request would 404. Keep the default route, or set \`convex.betterAuth.authClient\` to a client created with \`basePath: "${authRoute}"\`.`
58
60
  );
59
61
  }
60
62
  if (input.authClient && !authClientModuleExists(input.authClient, input.rootDir)) {
@@ -93,7 +95,7 @@ function integrationWarnings(flags, isInstalled) {
93
95
  }
94
96
  if (flags.polar && !isInstalled("@polar-sh/checkout")) {
95
97
  warnings.push(
96
- "`@convex-dev/polar` is installed but its peer `@polar-sh/checkout` is not \u2014 `<CheckoutLink>` imports it and the build fails the moment the component is used. Run `npm install @polar-sh/checkout`."
98
+ "`@convex-dev/polar` is installed but its peer `@polar-sh/checkout` is not \u2014 `<CheckoutLink>` imports it and the build fails the moment the component is used. Run `npm install @polar-sh/checkout@~0.4.0`."
97
99
  );
98
100
  }
99
101
  return warnings;
@@ -110,11 +112,16 @@ function formatStartupSummary(url, functionsDir, integrations) {
110
112
  return `Convex ${url || "(no URL)"} \xB7 functions: ${functionsDir}/ \xB7 integrations: ${enabled.join(", ") || "none"}`;
111
113
  }
112
114
  function isPackageInstalled(id, rootDir) {
113
- for (const base of [join(rootDir, "package.json"), import.meta.url]) {
114
- const lookupPaths = createRequire(base).resolve.paths(id) ?? [];
115
- if (lookupPaths.some((dir) => existsSync(join(dir, id, "package.json")))) return true;
115
+ return [rootDir, dirname(fileURLToPath(import.meta.url))].some(
116
+ (base) => nodeModulesPaths(base).some((dir) => existsSync(join(dir, id, "package.json")))
117
+ );
118
+ }
119
+ function nodeModulesPaths(dir) {
120
+ const paths = [];
121
+ for (let current = dir; ; current = dirname(current)) {
122
+ if (basename(current) !== "node_modules") paths.push(join(current, "node_modules"));
123
+ if (dirname(current) === current) return paths;
116
124
  }
117
- return false;
118
125
  }
119
126
  function resolveDeploymentUrls(options, env) {
120
127
  return {
@@ -122,6 +129,29 @@ function resolveDeploymentUrls(options, env) {
122
129
  siteUrl: stripTrailingSlashes(options.siteUrl || env.NUXT_PUBLIC_CONVEX_SITE_URL || env.CONVEX_SITE_URL || "")
123
130
  };
124
131
  }
132
+ function deploymentWarnings(options, env) {
133
+ const warnings = [];
134
+ const cliUrl = stripTrailingSlashes(env.CONVEX_URL || "");
135
+ const source = options.url ? "`convex.url`" : env.NUXT_PUBLIC_CONVEX_URL ? "NUXT_PUBLIC_CONVEX_URL" : "";
136
+ const { url } = resolveDeploymentUrls(options, env);
137
+ if (cliUrl && source && url !== cliUrl) {
138
+ const stale = [["`convex.url`", options.url], ["NUXT_PUBLIC_CONVEX_URL", env.NUXT_PUBLIC_CONVEX_URL]].filter(([, value]) => value && stripTrailingSlashes(value) !== cliUrl).map(([name]) => name);
139
+ warnings.push(
140
+ `The app calls ${url} (from ${source}), but \`npx convex dev\` pushes your functions to ${cliUrl} (CONVEX_URL). New or changed functions will fail with "Could not find public function". Remove ${stale.join(" and ")} to follow \`convex dev\`, or point ${source} at ${cliUrl}.`
141
+ );
142
+ }
143
+ return warnings;
144
+ }
145
+ function deploymentEnv(rootDir, env, dev) {
146
+ if (!dev) return env;
147
+ let local;
148
+ try {
149
+ local = parseEnv(readFileSync(join(rootDir, ".env.local"), "utf8"));
150
+ } catch {
151
+ return env;
152
+ }
153
+ return { ...local, ...env };
154
+ }
125
155
 
126
156
  const logger$1 = useLogger("nuxt-convex-module");
127
157
  const DEFAULT_FUNCTIONS_DIR = "convex";
@@ -164,7 +194,7 @@ function getConvexAliases(rootDir) {
164
194
  };
165
195
  }
166
196
 
167
- const CODEGEN_GUARDED_TEMPLATES = ["nuxt-convex-module-provide-api.mjs", "types/nuxt-convex-module-api-fallback.d.ts"];
197
+ const CODEGEN_GUARDED_TEMPLATES = ["types/nuxt-convex-module-api-fallback.d.ts"];
168
198
  function watchConvexCodegen(nuxt) {
169
199
  if (!nuxt.options.dev) return;
170
200
  const generatedDir = join(nuxt.options.rootDir, resolveFunctionsDir(nuxt.options.rootDir), "_generated");
@@ -175,9 +205,9 @@ function watchConvexCodegen(nuxt) {
175
205
  });
176
206
  }
177
207
 
178
- const PLAIN_NUXT_DEV = /^(?:npx )?(?:nuxt|nuxi) dev(?: [^&|;]*)?$/;
208
+ const PLAIN_NUXT_DEV = /^(?:npx )?(?:nuxt|nuxi) dev(?: [^&|;"']*)?$/;
179
209
  function combinedDevScript(nuxtCommand) {
180
- return `convex dev --start '${nuxtCommand}'`;
210
+ return `convex dev --start "${nuxtCommand}"`;
181
211
  }
182
212
  function setupDevScript(rootDir) {
183
213
  const path = join(rootDir, "package.json");
@@ -207,8 +237,14 @@ function setupDevScript(rootDir) {
207
237
  return { changed: true, from: dev, to };
208
238
  }
209
239
 
210
- function isAutoImported(name, mode) {
211
- return mode === true || mode === "prefixed" && /convex/i.test(name);
240
+ function prefixedName(name) {
241
+ if (/convex/i.test(name)) return name;
242
+ return /^[A-Z]/.test(name) ? `Convex${name}` : name.replace(/[A-Z]/, (letter) => `Convex${letter}`);
243
+ }
244
+ function autoImportedAs(name, mode, registered) {
245
+ if (mode !== "prefixed") return mode ? name : void 0;
246
+ const prefixed = prefixedName(name);
247
+ return prefixed !== name && registered.includes(prefixed) ? void 0 : prefixed;
212
248
  }
213
249
  const vue = (file) => `runtime/vue/${file}`;
214
250
  const composable = (file) => vue(`composables/${file}`);
@@ -236,9 +272,6 @@ const APP_IMPORTS = {
236
272
  { name: "useConvexStorageUrl", from: composable("use-storage-url") },
237
273
  { name: "useConvexAuth", from: vue("auth/index") },
238
274
  { name: "provideConvexAuth", from: vue("auth/index") },
239
- { name: "provideConvexApi", from: vue("provide") },
240
- { name: "useConvexApi", from: vue("provide") },
241
- { name: "useConvexNamespace", from: vue("provide") },
242
275
  { name: "usePreloadedQuery", from: vue("hydration") },
243
276
  // Nuxt-only (imports `#app`), hence under runtime/nuxt/ — see PARITY.md.
244
277
  // Its types too, so `import type { AsyncQueryReturn } from '#imports'` works
@@ -327,15 +360,13 @@ const module$1 = defineNuxtModule({
327
360
  configKey: "convex",
328
361
  // Surfaced by Nuxt DevTools and the nuxt/modules registry.
329
362
  docs: "https://nuxt-convex-module.dev",
330
- // `moduleDependencies` (below) is a Nuxt >= 4.1 feature — fail fast with a
331
- // clear kit error on older Nuxt instead of silently skipping nuxt-security.
363
+ // The server code is written against `nuxt/server`, which ships in 4.6.
332
364
  compatibility: {
333
- nuxt: ">=4.1.0"
365
+ nuxt: ">=4.6.0"
334
366
  }
335
367
  },
336
368
  defaults: {
337
369
  autoImports: true,
338
- authRoute: "/api/auth",
339
370
  devtools: true,
340
371
  devScript: true
341
372
  },
@@ -349,27 +380,28 @@ const module$1 = defineNuxtModule({
349
380
  async setup(options, nuxt) {
350
381
  const resolver = createResolver(import.meta.url);
351
382
  nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
383
+ nuxt.options.vite.resolve ??= {};
384
+ nuxt.options.vite.resolve.dedupe = [...nuxt.options.vite.resolve.dedupe ?? [], "convex"];
352
385
  const { url, siteUrl } = applyRuntimeConfig(nuxt, options);
386
+ const betterAuthOptions = readBetterAuthOptions(options);
353
387
  const diagnostics = validateModuleOptions({
354
388
  url,
355
389
  siteUrl,
356
- authRoute: options.authRoute || "/api/auth",
390
+ authRoute: betterAuthOptions.authRoute || "/api/auth",
357
391
  loginPath: nuxt.options.runtimeConfig.public.convex.loginPath,
358
- authClient: typeof options.betterAuth === "object" ? options.betterAuth.authClient : void 0,
392
+ authClient: betterAuthOptions.authClient,
359
393
  rootDir: nuxt.options.rootDir
360
394
  });
361
395
  for (const message of diagnostics.errors) logger.error(message);
362
396
  for (const message of diagnostics.warnings) logger.warn(message);
363
- options.authRoute = diagnostics.authRoute;
364
397
  nuxt.options.runtimeConfig.public.convex.loginPath = diagnostics.loginPath;
365
398
  registerAuthClientAlias(resolver, nuxt, options);
366
399
  registerConvexAliases(nuxt);
367
- registerConvexApiPlugin(resolver, nuxt);
368
400
  registerConvexTypeFallback(nuxt);
369
401
  const autoImports = options.autoImports ?? true;
370
402
  registerVueComposables(resolver, autoImports);
371
403
  registerAuthComponents(resolver, autoImports);
372
- const integrations = registerIntegrations(resolver, nuxt, options, autoImports);
404
+ const integrations = registerIntegrations(resolver, nuxt, options, autoImports, diagnostics.authRoute);
373
405
  watchConvexCodegen(nuxt);
374
406
  if (nuxt.options.dev && options.devtools !== false && isDevtoolsUiEnabled(nuxt)) {
375
407
  const { setupDevtools } = await import('./chunks/index.mjs');
@@ -389,15 +421,22 @@ const module$1 = defineNuxtModule({
389
421
  const result = setupDevScript(nuxt.options.rootDir);
390
422
  if (result.changed) {
391
423
  logger.info(`Set \`scripts.dev\` to \`${result.to}\` \u2014 one command now runs Convex beside Nuxt, no .env needed locally. Set \`convex.devScript: false\` to keep your own script.`);
424
+ hintAgent();
392
425
  }
393
426
  }
394
427
  }
395
428
  });
429
+ let agentHinted = false;
430
+ function hintAgent() {
431
+ if (agentHinted || !detectAgent().name) return;
432
+ agentHinted = true;
433
+ logger.info("An AI agent is running Nuxt. `CONVEX_AGENT_MODE=anonymous npx convex dev --once` sets up a local Convex deployment with no prompts and no account, writes .env.local and the codegen, then exits. For the setup steps and this module's API: `npx skills add https://nuxt-convex-module.dev --skill nuxt-convex-module -y`.");
434
+ }
396
435
  function isDevtoolsUiEnabled(nuxt) {
397
436
  const devtools = nuxt.options.devtools;
398
437
  return typeof devtools === "boolean" ? devtools : devtools?.enabled !== false;
399
438
  }
400
- function registerIntegrations(resolver, nuxt, options, autoImports) {
439
+ function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
401
440
  const report = (key, pkg, state) => {
402
441
  if (state.missingPackage) {
403
442
  logger.error(`\`convex.${key}\` is enabled but \`${pkg}\` is not installed. Run \`npm install ${pkg}\` or remove the option.`);
@@ -408,7 +447,7 @@ function registerIntegrations(resolver, nuxt, options, autoImports) {
408
447
  const resolve = (key, pkg) => report(key, pkg, resolveIntegrationState(options[key], isPackageInstalled(pkg, rootDir), isDeclaredDependency(pkg, rootDir)));
409
448
  const betterAuth = resolve("betterAuth", "@convex-dev/better-auth");
410
449
  if (betterAuth) {
411
- registerBetterAuth(resolver, options.authRoute || "/api/auth", autoImports);
450
+ registerBetterAuth(resolver, authRoute, autoImports);
412
451
  } else {
413
452
  registerBaseConvexClient(resolver);
414
453
  }
@@ -445,15 +484,25 @@ function resolveSecurityState(nuxt, explicit) {
445
484
  return resolveIntegrationState(explicit, installed, declared);
446
485
  }
447
486
  function registerSecurity(resolver) {
448
- addServerPlugin(resolver.resolve("./runtime/nuxt/security"));
487
+ const plugin = resolver.resolve("./runtime/nuxt/security");
488
+ addNitroPlugin({ nitro2: plugin, nitro3: plugin });
489
+ }
490
+ function readBetterAuthOptions(options) {
491
+ if ("authRoute" in options) logger.error("`convex.authRoute` moved to `convex.betterAuth.authRoute`.");
492
+ return typeof options.betterAuth === "object" ? options.betterAuth : {};
449
493
  }
450
494
  function applyRuntimeConfig(nuxt, options) {
451
- const resolved = resolveDeploymentUrls(options, process.env);
495
+ const env = deploymentEnv(nuxt.options.rootDir, process.env, nuxt.options.dev);
496
+ const resolved = resolveDeploymentUrls(options, env);
452
497
  const url = nuxt.options._prepare ? "" : resolved.url;
498
+ if (nuxt.options.dev && url) {
499
+ for (const message of deploymentWarnings(options, env)) logger.warn(message);
500
+ }
453
501
  if (!url && !nuxt.options._prepare) {
454
502
  logger.warn(
455
- "No Convex deployment URL configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config. Note: `npx convex dev` writes CONVEX_URL to .env.local, which Nuxt does not read without `nuxt dev --dotenv .env.local`."
503
+ "No Convex deployment URL configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config, or run `npx convex dev`: it writes CONVEX_URL to .env.local, which the module reads in development."
456
504
  );
505
+ hintAgent();
457
506
  }
458
507
  const siteUrl = resolved.siteUrl;
459
508
  const crossDomainCallbackRoute = typeof options.betterAuth === "object" && options.betterAuth.crossDomainCallbackRoute || "";
@@ -466,7 +515,6 @@ function applyRuntimeConfig(nuxt, options) {
466
515
  crossDomainCallbackRoute,
467
516
  loginPath
468
517
  };
469
- runtimeConfig.convex = { ...runtimeConfig.convex, siteUrl };
470
518
  return { url, siteUrl };
471
519
  }
472
520
  function registerConvexAliases(nuxt) {
@@ -486,41 +534,24 @@ function registerAuthClientAlias(resolver, nuxt, options) {
486
534
  nuxt.options.alias["#convex/auth-client"] = target;
487
535
  nuxt.options.nitro.alias["#convex/auth-client"] = target;
488
536
  }
489
- function registerConvexApiPlugin(resolver, nuxt) {
537
+ function registerConvexTypeFallback(nuxt) {
490
538
  const functionsDir = resolveFunctionsDir(nuxt.options.rootDir);
491
- const provideModule = resolver.resolve("./runtime/vue/provide");
492
539
  let notifiedMissing = false;
493
- addPluginTemplate({
494
- filename: "nuxt-convex-module-provide-api.mjs",
540
+ addTypeTemplate({
541
+ filename: "types/nuxt-convex-module-api-fallback.d.ts",
495
542
  getContents: () => {
496
- if (!hasGeneratedApi(nuxt.options.rootDir, functionsDir)) {
497
- if (nuxt.options.dev && !nuxt.options._prepare && !notifiedMissing) {
498
- notifiedMissing = true;
499
- logger.info(`Convex codegen not found in \`${functionsDir}/_generated\` \u2014 run \`npx convex dev\`. Convex features no-op until it exists.`);
500
- }
501
- return "import { defineNuxtPlugin } from '#app'\nexport default defineNuxtPlugin(() => {})\n";
543
+ const generated = hasGeneratedApi(nuxt.options.rootDir, functionsDir);
544
+ if (!generated && nuxt.options.dev && !nuxt.options._prepare && !notifiedMissing) {
545
+ notifiedMissing = true;
546
+ logger.info(`Convex codegen not found in \`${functionsDir}/_generated\` \u2014 run \`npx convex dev\`. \`#convex/api\` is typed \`any\` until it exists.`);
547
+ hintAgent();
502
548
  }
503
- if (notifiedMissing) {
549
+ if (generated && notifiedMissing) {
504
550
  notifiedMissing = false;
505
- logger.success("Convex codegen detected \u2014 generated `api` wired app-wide.");
551
+ logger.success("Convex codegen detected \u2014 `#convex/api` is typed from it.");
506
552
  }
507
- return [
508
- "import { defineNuxtPlugin } from '#app'",
509
- "import { api } from '#convex/api'",
510
- `import { provideConvexApi } from ${JSON.stringify(provideModule)}`,
511
- "export default defineNuxtPlugin((nuxtApp) => {",
512
- " provideConvexApi(api, nuxtApp.vueApp)",
513
- "})",
514
- ""
515
- ].join("\n");
553
+ return convexTypeFallbackContents(generated, functionsDir);
516
554
  }
517
- });
518
- }
519
- function registerConvexTypeFallback(nuxt) {
520
- const functionsDir = resolveFunctionsDir(nuxt.options.rootDir);
521
- addTypeTemplate({
522
- filename: "types/nuxt-convex-module-api-fallback.d.ts",
523
- getContents: () => convexTypeFallbackContents(hasGeneratedApi(nuxt.options.rootDir, functionsDir), functionsDir)
524
555
  }, { nuxt: true, nitro: true });
525
556
  }
526
557
  function registerVueComposables(resolver, autoImports) {
@@ -530,16 +561,20 @@ function registerAuthComponents(resolver, autoImports) {
530
561
  registerComponents(resolver, "core", autoImports);
531
562
  }
532
563
  function registerIntegrationImports(resolver, integration, autoImports) {
533
- const kept = (entries) => entries.filter((entry) => isAutoImported(entry.name, autoImports)).map((entry) => ({ ...entry, from: resolver.resolve(`./${entry.from}`) }));
564
+ const kept = (entries) => entries.flatMap((entry) => {
565
+ const as = autoImportedAs(entry.name, autoImports, entries.map((e) => e.name));
566
+ return as ? [{ ...entry, as, from: resolver.resolve(`./${entry.from}`) }] : [];
567
+ });
534
568
  const app = kept(APP_IMPORTS[integration]);
535
569
  const server = kept(SERVER_IMPORTS[integration]);
536
570
  if (app.length > 0) addImports(app);
537
571
  if (server.length > 0) addServerImports(server);
538
572
  }
539
573
  function registerComponents(resolver, integration, autoImports) {
540
- for (const { name, from } of APP_COMPONENTS[integration]) {
541
- if (!isAutoImported(name, autoImports)) continue;
542
- addComponent({ name, filePath: resolver.resolve(`./${from}`), export: name });
574
+ const entries = APP_COMPONENTS[integration];
575
+ for (const { name, from } of entries) {
576
+ const as = autoImportedAs(name, autoImports, entries.map((e) => e.name));
577
+ if (as) addComponent({ name: as, filePath: resolver.resolve(`./${from}`), export: name });
543
578
  }
544
579
  }
545
580
  const AUTH_PROXY_SECURITY_RULES = {
@@ -553,7 +588,7 @@ function registerBetterAuth(resolver, authRoute, autoImports) {
553
588
  registerComponents(resolver, "betterAuth", autoImports);
554
589
  addServerHandler({
555
590
  route: `${authRoute}/**`,
556
- handler: resolver.resolve("./runtime/better-auth/nuxt/proxy")
591
+ handler: { nuxt: resolver.resolve("./runtime/better-auth/nuxt/proxy") }
557
592
  });
558
593
  extendRouteRules(`${authRoute}/**`, {
559
594
  cache: false,
@@ -6,7 +6,7 @@ export function provideConvexAuthFromAuth0(options = {}) {
6
6
  const client = options.client ?? useConvexOrThrow("provideConvexAuthFromAuth0");
7
7
  return provideConvexAuth({ client, useAuth: useAuthFromAuth0 });
8
8
  }
9
- export const ConvexProviderWithAuth0 = defineComponent({
9
+ export const ConvexProviderWithAuth0 = /* @__PURE__ */ defineComponent({
10
10
  name: "ConvexProviderWithAuth0",
11
11
  props: {
12
12
  client: { type: Object, default: void 0 }
@@ -1,18 +1,7 @@
1
- import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp, useRequestEvent, useRuntimeConfig } from "#app";
1
+ import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
2
  import { watch } from "vue";
3
3
  import { useBetterAuth } from "../vue/use-better-auth.js";
4
- import { convexAuth } from "./server.js";
5
- function loginTarget(to, loginPath) {
6
- return { path: loginPath, query: { redirect: to.fullPath } };
7
- }
8
- export async function serverGuard(to, loginPath) {
9
- const nuxtApp = useNuxtApp();
10
- const event = useRequestEvent();
11
- const authed = event ? await convexAuth(event).isAuthenticated() : false;
12
- if (!authed && to.path !== loginPath) {
13
- return nuxtApp.runWithContext(() => navigateTo(loginTarget(to, loginPath)));
14
- }
15
- }
4
+ import { loginTarget, serverGuard } from "./server-guard.js";
16
5
  function waitForSession(isPending) {
17
6
  return new Promise((resolve) => {
18
7
  const stop = watch(
@@ -1,22 +1,22 @@
1
1
  /**
2
- * Proxy all `${convex.authRoute}/**` requests (default `/api/auth/**`) to the
2
+ * Proxy all `${convex.betterAuth.authRoute}/**` requests (default `/api/auth/**`) to the
3
3
  * Convex site URL. This keeps auth cookies on the same origin (no CORS issues).
4
4
  *
5
- * The site URL is `convexAuth`'s to resolve: the private `runtimeConfig.convex.siteUrl`,
6
- * then the public one, then the environment — so a container built without it and
7
- * started with `NUXT_PUBLIC_CONVEX_SITE_URL` works. Reading only the private key here
8
- * used to fail every auth request in exactly that deployment.
5
+ * The site URL is `convexAuth`'s to resolve: `runtimeConfig.public.convex.siteUrl`,
6
+ * then the environment — so a container built without it and started with
7
+ * `NUXT_PUBLIC_CONVEX_SITE_URL` works.
9
8
  *
10
9
  * The method list matches the nuxt-security route rule the module registers on this
11
10
  * route; enforcing it here too keeps the proxy closed to `PUT`/`DELETE` probes when
12
11
  * nuxt-security is not installed.
13
12
  *
14
- * The path check: h3 routes the request's raw path, and the handler forwards the
15
- * URL-normalised one. `/api/auth/../../x` (or its `%2e%2e` spelling) matches this
16
- * route and normalises to `/x`, which the proxy would send to the site origin —
17
- * any HTTP action on the deployment, reached through the app's origin with the
18
- * app's cookies attached. Next.js normalises before routing, so upstream's
19
- * handler never sees such a path; Nitro does not, so it is refused here.
13
+ * The path check: Nitro 2 routes the request's raw path, and the handler forwards
14
+ * the URL-normalised one. `/api/auth/../../x` (or its `%2e%2e` spelling) matches
15
+ * this route and normalises to `/x`, which the proxy would send to the site
16
+ * origin — any HTTP action on the deployment, reached through the app's origin
17
+ * with the app's cookies attached. Next.js normalises before routing, so
18
+ * upstream's handler never sees such a path; Nitro 2 does not, so a dot segment
19
+ * in the part of the path this route matched is refused here.
20
20
  */
21
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Response>>;
21
+ declare const _default: import("nuxt/server").EventHandler<Promise<Response>>;
22
22
  export default _default;
@@ -1,11 +1,13 @@
1
- import { assertMethod, createError, defineEventHandler, toWebRequest } from "h3";
1
+ import { createError, defineEventHandler, getRouterParam } from "nuxt/server";
2
2
  import { convexAuth } from "./server.js";
3
+ const METHODS = ["GET", "HEAD", "POST", "OPTIONS"];
3
4
  export default defineEventHandler(async (event) => {
4
- assertMethod(event, ["GET", "HEAD", "POST", "OPTIONS"]);
5
- const routed = event.path.split("?")[0];
6
- const forwarded = new URL(toWebRequest(event).url).pathname;
7
- if (forwarded !== routed) {
8
- throw createError({ statusCode: 400, statusMessage: "Bad Request" });
5
+ if (!METHODS.includes(event.req.method)) {
6
+ throw createError({ status: 405, statusText: "Method Not Allowed" });
7
+ }
8
+ const matched = getRouterParam(event, "_", { decode: true }) ?? "";
9
+ if (matched.split(/[/\\]/).some((segment) => segment === "." || segment === "..")) {
10
+ throw createError({ status: 400, statusText: "Bad Request" });
9
11
  }
10
12
  return convexAuth(event).handler();
11
13
  });
@@ -0,0 +1,12 @@
1
+ interface GuardedRoute {
2
+ path: string;
3
+ fullPath: string;
4
+ }
5
+ /** Where a visitor who isn't signed in goes: the login page, with the page they asked for in `?redirect=`. */
6
+ export declare function loginTarget(to: GuardedRoute, loginPath: string): {
7
+ path: string;
8
+ query: {
9
+ redirect: string;
10
+ };
11
+ };
12
+ export {};
@@ -0,0 +1,13 @@
1
+ import { navigateTo, useNuxtApp, useRequestEvent } from "#app";
2
+ import { convexAuth } from "./server.js";
3
+ export function loginTarget(to, loginPath) {
4
+ return { path: loginPath, query: { redirect: to.fullPath } };
5
+ }
6
+ export async function serverGuard(to, loginPath) {
7
+ const nuxtApp = useNuxtApp();
8
+ const event = useRequestEvent();
9
+ const authed = event ? await convexAuth(event).isAuthenticated() : false;
10
+ if (!authed && to.path !== loginPath) {
11
+ return nuxtApp.runWithContext(() => navigateTo(loginTarget(to, loginPath)));
12
+ }
13
+ }
@@ -1,16 +1,28 @@
1
1
  /**
2
2
  * Server-side Better Auth + Convex integration for Nuxt.
3
3
  *
4
- * Provides {@link convexAuth} for Nuxt server handlers: it takes the H3
5
- * `H3Event` every Nuxt server route, API handler and server middleware
6
- * receives, and returns a request-scoped, authenticated Convex client.
4
+ * Provides {@link convexAuth} for Nuxt server handlers: it takes the event
5
+ * every Nuxt server route, API handler and server middleware receives, and
6
+ * returns a request-scoped, authenticated Convex client.
7
7
  *
8
8
  * @module better-auth/server
9
9
  */
10
- import type { H3Event } from 'h3';
10
+ import type { RequestEvent } from 'nuxt/schema';
11
11
  import type { GetTokenOptions } from '@convex-dev/better-auth/utils';
12
12
  import type { FunctionReference, FunctionReturnType } from 'convex/server';
13
13
  import type { Preloaded } from '../../vue/hydration.js';
14
+ /**
15
+ * The server event {@link convexAuth} reads: the one `defineEventHandler` from
16
+ * `nuxt/server` passes, whose `req` is a web `Request`, or the h3 event a
17
+ * Nuxt 4 route defined with the auto-imported `defineEventHandler`, and
18
+ * `useRequestEvent()` during SSR, still pass, which carries `headers`.
19
+ *
20
+ * @public
21
+ */
22
+ export type ConvexAuthEvent = Pick<RequestEvent, 'req' | 'context'> | {
23
+ headers: Headers;
24
+ context: Record<string, unknown>;
25
+ };
14
26
  type OptionalArgs<FuncRef extends FunctionReference<any, any>> = FuncRef['_args'] extends Record<string, never> ? [args?: Record<string, never>] : [args: FuncRef['_args']];
15
27
  /**
16
28
  * Options for {@link convexAuth}.
@@ -28,8 +40,8 @@ export type ConvexAuthOptions = GetTokenOptions & {
28
40
  */
29
41
  convexUrl?: string;
30
42
  /**
31
- * Override the Convex site URL. Defaults to `runtimeConfig.convex.siteUrl`,
32
- * then `runtimeConfig.public.convex.siteUrl`, then the
43
+ * Override the Convex site URL. Defaults to
44
+ * `runtimeConfig.public.convex.siteUrl`, then the
33
45
  * `NUXT_PUBLIC_CONVEX_SITE_URL` process variable.
34
46
  */
35
47
  convexSiteUrl?: string;
@@ -58,7 +70,7 @@ export interface ConvexAuthService {
58
70
  /**
59
71
  * Create a per-request Better Auth + Convex helper for Nuxt server code.
60
72
  *
61
- * Adapts the Better Auth + Convex server helper pattern to H3 events.
73
+ * Adapts the Better Auth + Convex server helper pattern to Nuxt server events.
62
74
  *
63
75
  * Call once at the top of a server route / event handler and destructure the
64
76
  * helpers you need. The auth token is fetched at most once per request — the
@@ -87,18 +99,18 @@ export interface ConvexAuthService {
87
99
  * })
88
100
  * ```
89
101
  *
90
- * @param event - The H3 event from a Nuxt server route or middleware.
102
+ * @param event - The event from a Nuxt server route or middleware, or `useRequestEvent()` during SSR. See {@link ConvexAuthEvent}.
91
103
  * @param opts - Optional {@link ConvexAuthOptions}.
92
104
  *
93
105
  * @public
94
106
  */
95
- export declare function convexAuth(event: H3Event, opts?: ConvexAuthOptions): ConvexAuthService;
107
+ export declare function convexAuth(event: ConvexAuthEvent, opts?: ConvexAuthOptions): ConvexAuthService;
96
108
  /**
97
109
  * Upstream-named alias of {@link convexAuth} — the mechanical rename target
98
110
  * for `@convex-dev/better-auth/nextjs`'s `convexBetterAuthNextJs` (framework
99
111
  * suffix `NextJs` → `Nuxt`). Unlike upstream's module-level factory (which
100
112
  * reads request state ambiently via `next/headers`), Nitro has no ambient
101
- * per-request context, so the H3 `event` is passed explicitly.
113
+ * per-request context, so the `event` is passed explicitly.
102
114
  *
103
115
  * @public
104
116
  */
@@ -1,4 +1,3 @@
1
- import { getRequestHeaders, toWebRequest } from "h3";
2
1
  import { getToken } from "@convex-dev/better-auth/utils";
3
2
  import { getConvexRuntimeConfig } from "../../nuxt/config.js";
4
3
  import { fetchAction, fetchMutation, fetchQuery, preloadQuery } from "../../nuxt/index.js";
@@ -65,7 +64,16 @@ const handler = async (request, siteUrl) => {
65
64
  headers: responseHeaders
66
65
  });
67
66
  };
68
- const nuxtHandler = (event, siteUrl) => () => handler(toWebRequest(event), siteUrl);
67
+ const requestOf = (event) => "req" in event && event.req instanceof Request ? event.req : void 0;
68
+ const nuxtHandler = (event, siteUrl) => () => {
69
+ const request = requestOf(event);
70
+ if (!request) {
71
+ throw new Error(
72
+ "[nuxt-convex-module] convexAuth(event).handler() needs the event `defineEventHandler` from `nuxt/server` passes, whose `req` is a web Request."
73
+ );
74
+ }
75
+ return handler(request, siteUrl);
76
+ };
69
77
  const getArgsAndOptions = (args, token) => {
70
78
  return [args[0], { token }];
71
79
  };
@@ -78,8 +86,7 @@ export function convexAuth(event, opts) {
78
86
  const cachedGetToken = ({ forceRefresh } = {}) => {
79
87
  const cached = tokenCache[cacheKey];
80
88
  if (cached && !forceRefresh) return cached;
81
- const headers = getRequestHeaders(event);
82
- const mutableHeaders = new Headers(headers);
89
+ const mutableHeaders = new Headers(requestOf(event)?.headers ?? event.headers);
83
90
  mutableHeaders.delete("content-length");
84
91
  mutableHeaders.delete("transfer-encoding");
85
92
  mutableHeaders.set("accept-encoding", "identity");