nuxt-convex-module 0.11.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.
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
@@ -122,11 +127,6 @@ interface ModuleOptions {
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.11.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,4 +1,4 @@
1
- import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addServerPlugin, addPluginTemplate, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
1
+ import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addNitroPlugin, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
2
2
  import { dirname, join, isAbsolute, basename } from 'node:path';
3
3
  import { detectAgent } from 'std-env';
4
4
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
@@ -52,11 +52,11 @@ function validateModuleOptions(input) {
52
52
  `\`convex.siteUrl\` ("${input.siteUrl}") does not look like a valid http(s) URL.`
53
53
  );
54
54
  }
55
- const authRoute = normalizeRoutePath(input.authRoute, "convex.authRoute", warnings);
55
+ const authRoute = normalizeRoutePath(input.authRoute, "convex.betterAuth.authRoute", warnings);
56
56
  const loginPath = normalizeRoutePath(input.loginPath ?? "/login", "convex.betterAuth.loginPath", warnings);
57
57
  if (authRoute !== "/api/auth" && !input.authClient) {
58
58
  errors.push(
59
- `\`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}"\`.`
60
60
  );
61
61
  }
62
62
  if (input.authClient && !authClientModuleExists(input.authClient, input.rootDir)) {
@@ -95,7 +95,7 @@ function integrationWarnings(flags, isInstalled) {
95
95
  }
96
96
  if (flags.polar && !isInstalled("@polar-sh/checkout")) {
97
97
  warnings.push(
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`."
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`."
99
99
  );
100
100
  }
101
101
  return warnings;
@@ -129,6 +129,19 @@ function resolveDeploymentUrls(options, env) {
129
129
  siteUrl: stripTrailingSlashes(options.siteUrl || env.NUXT_PUBLIC_CONVEX_SITE_URL || env.CONVEX_SITE_URL || "")
130
130
  };
131
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
+ }
132
145
  function deploymentEnv(rootDir, env, dev) {
133
146
  if (!dev) return env;
134
147
  let local;
@@ -181,7 +194,7 @@ function getConvexAliases(rootDir) {
181
194
  };
182
195
  }
183
196
 
184
- 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"];
185
198
  function watchConvexCodegen(nuxt) {
186
199
  if (!nuxt.options.dev) return;
187
200
  const generatedDir = join(nuxt.options.rootDir, resolveFunctionsDir(nuxt.options.rootDir), "_generated");
@@ -224,8 +237,14 @@ function setupDevScript(rootDir) {
224
237
  return { changed: true, from: dev, to };
225
238
  }
226
239
 
227
- function isAutoImported(name, mode) {
228
- 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;
229
248
  }
230
249
  const vue = (file) => `runtime/vue/${file}`;
231
250
  const composable = (file) => vue(`composables/${file}`);
@@ -253,9 +272,6 @@ const APP_IMPORTS = {
253
272
  { name: "useConvexStorageUrl", from: composable("use-storage-url") },
254
273
  { name: "useConvexAuth", from: vue("auth/index") },
255
274
  { name: "provideConvexAuth", from: vue("auth/index") },
256
- { name: "provideConvexApi", from: vue("provide") },
257
- { name: "useConvexApi", from: vue("provide") },
258
- { name: "useConvexNamespace", from: vue("provide") },
259
275
  { name: "usePreloadedQuery", from: vue("hydration") },
260
276
  // Nuxt-only (imports `#app`), hence under runtime/nuxt/ — see PARITY.md.
261
277
  // Its types too, so `import type { AsyncQueryReturn } from '#imports'` works
@@ -344,15 +360,13 @@ const module$1 = defineNuxtModule({
344
360
  configKey: "convex",
345
361
  // Surfaced by Nuxt DevTools and the nuxt/modules registry.
346
362
  docs: "https://nuxt-convex-module.dev",
347
- // `moduleDependencies` (below) is a Nuxt >= 4.1 feature — fail fast with a
348
- // 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.
349
364
  compatibility: {
350
- nuxt: ">=4.1.0"
365
+ nuxt: ">=4.6.0"
351
366
  }
352
367
  },
353
368
  defaults: {
354
369
  autoImports: true,
355
- authRoute: "/api/auth",
356
370
  devtools: true,
357
371
  devScript: true
358
372
  },
@@ -369,26 +383,25 @@ const module$1 = defineNuxtModule({
369
383
  nuxt.options.vite.resolve ??= {};
370
384
  nuxt.options.vite.resolve.dedupe = [...nuxt.options.vite.resolve.dedupe ?? [], "convex"];
371
385
  const { url, siteUrl } = applyRuntimeConfig(nuxt, options);
386
+ const betterAuthOptions = readBetterAuthOptions(options);
372
387
  const diagnostics = validateModuleOptions({
373
388
  url,
374
389
  siteUrl,
375
- authRoute: options.authRoute || "/api/auth",
390
+ authRoute: betterAuthOptions.authRoute || "/api/auth",
376
391
  loginPath: nuxt.options.runtimeConfig.public.convex.loginPath,
377
- authClient: typeof options.betterAuth === "object" ? options.betterAuth.authClient : void 0,
392
+ authClient: betterAuthOptions.authClient,
378
393
  rootDir: nuxt.options.rootDir
379
394
  });
380
395
  for (const message of diagnostics.errors) logger.error(message);
381
396
  for (const message of diagnostics.warnings) logger.warn(message);
382
- options.authRoute = diagnostics.authRoute;
383
397
  nuxt.options.runtimeConfig.public.convex.loginPath = diagnostics.loginPath;
384
398
  registerAuthClientAlias(resolver, nuxt, options);
385
399
  registerConvexAliases(nuxt);
386
- registerConvexApiPlugin(resolver, nuxt);
387
400
  registerConvexTypeFallback(nuxt);
388
401
  const autoImports = options.autoImports ?? true;
389
402
  registerVueComposables(resolver, autoImports);
390
403
  registerAuthComponents(resolver, autoImports);
391
- const integrations = registerIntegrations(resolver, nuxt, options, autoImports);
404
+ const integrations = registerIntegrations(resolver, nuxt, options, autoImports, diagnostics.authRoute);
392
405
  watchConvexCodegen(nuxt);
393
406
  if (nuxt.options.dev && options.devtools !== false && isDevtoolsUiEnabled(nuxt)) {
394
407
  const { setupDevtools } = await import('./chunks/index.mjs');
@@ -423,7 +436,7 @@ function isDevtoolsUiEnabled(nuxt) {
423
436
  const devtools = nuxt.options.devtools;
424
437
  return typeof devtools === "boolean" ? devtools : devtools?.enabled !== false;
425
438
  }
426
- function registerIntegrations(resolver, nuxt, options, autoImports) {
439
+ function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
427
440
  const report = (key, pkg, state) => {
428
441
  if (state.missingPackage) {
429
442
  logger.error(`\`convex.${key}\` is enabled but \`${pkg}\` is not installed. Run \`npm install ${pkg}\` or remove the option.`);
@@ -434,7 +447,7 @@ function registerIntegrations(resolver, nuxt, options, autoImports) {
434
447
  const resolve = (key, pkg) => report(key, pkg, resolveIntegrationState(options[key], isPackageInstalled(pkg, rootDir), isDeclaredDependency(pkg, rootDir)));
435
448
  const betterAuth = resolve("betterAuth", "@convex-dev/better-auth");
436
449
  if (betterAuth) {
437
- registerBetterAuth(resolver, options.authRoute || "/api/auth", autoImports);
450
+ registerBetterAuth(resolver, authRoute, autoImports);
438
451
  } else {
439
452
  registerBaseConvexClient(resolver);
440
453
  }
@@ -471,11 +484,20 @@ function resolveSecurityState(nuxt, explicit) {
471
484
  return resolveIntegrationState(explicit, installed, declared);
472
485
  }
473
486
  function registerSecurity(resolver) {
474
- 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 : {};
475
493
  }
476
494
  function applyRuntimeConfig(nuxt, options) {
477
- const resolved = resolveDeploymentUrls(options, deploymentEnv(nuxt.options.rootDir, process.env, nuxt.options.dev));
495
+ const env = deploymentEnv(nuxt.options.rootDir, process.env, nuxt.options.dev);
496
+ const resolved = resolveDeploymentUrls(options, env);
478
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
+ }
479
501
  if (!url && !nuxt.options._prepare) {
480
502
  logger.warn(
481
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."
@@ -493,7 +515,6 @@ function applyRuntimeConfig(nuxt, options) {
493
515
  crossDomainCallbackRoute,
494
516
  loginPath
495
517
  };
496
- runtimeConfig.convex = { ...runtimeConfig.convex, siteUrl };
497
518
  return { url, siteUrl };
498
519
  }
499
520
  function registerConvexAliases(nuxt) {
@@ -513,42 +534,24 @@ function registerAuthClientAlias(resolver, nuxt, options) {
513
534
  nuxt.options.alias["#convex/auth-client"] = target;
514
535
  nuxt.options.nitro.alias["#convex/auth-client"] = target;
515
536
  }
516
- function registerConvexApiPlugin(resolver, nuxt) {
537
+ function registerConvexTypeFallback(nuxt) {
517
538
  const functionsDir = resolveFunctionsDir(nuxt.options.rootDir);
518
- const provideModule = resolver.resolve("./runtime/vue/provide");
519
539
  let notifiedMissing = false;
520
- addPluginTemplate({
521
- filename: "nuxt-convex-module-provide-api.mjs",
540
+ addTypeTemplate({
541
+ filename: "types/nuxt-convex-module-api-fallback.d.ts",
522
542
  getContents: () => {
523
- if (!hasGeneratedApi(nuxt.options.rootDir, functionsDir)) {
524
- if (nuxt.options.dev && !nuxt.options._prepare && !notifiedMissing) {
525
- notifiedMissing = true;
526
- logger.info(`Convex codegen not found in \`${functionsDir}/_generated\` \u2014 run \`npx convex dev\`. Convex features no-op until it exists.`);
527
- hintAgent();
528
- }
529
- 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();
530
548
  }
531
- if (notifiedMissing) {
549
+ if (generated && notifiedMissing) {
532
550
  notifiedMissing = false;
533
- logger.success("Convex codegen detected \u2014 generated `api` wired app-wide.");
551
+ logger.success("Convex codegen detected \u2014 `#convex/api` is typed from it.");
534
552
  }
535
- return [
536
- "import { defineNuxtPlugin } from '#app'",
537
- "import { api } from '#convex/api'",
538
- `import { provideConvexApi } from ${JSON.stringify(provideModule)}`,
539
- "export default defineNuxtPlugin((nuxtApp) => {",
540
- " provideConvexApi(api, nuxtApp.vueApp)",
541
- "})",
542
- ""
543
- ].join("\n");
553
+ return convexTypeFallbackContents(generated, functionsDir);
544
554
  }
545
- });
546
- }
547
- function registerConvexTypeFallback(nuxt) {
548
- const functionsDir = resolveFunctionsDir(nuxt.options.rootDir);
549
- addTypeTemplate({
550
- filename: "types/nuxt-convex-module-api-fallback.d.ts",
551
- getContents: () => convexTypeFallbackContents(hasGeneratedApi(nuxt.options.rootDir, functionsDir), functionsDir)
552
555
  }, { nuxt: true, nitro: true });
553
556
  }
554
557
  function registerVueComposables(resolver, autoImports) {
@@ -558,16 +561,20 @@ function registerAuthComponents(resolver, autoImports) {
558
561
  registerComponents(resolver, "core", autoImports);
559
562
  }
560
563
  function registerIntegrationImports(resolver, integration, autoImports) {
561
- 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
+ });
562
568
  const app = kept(APP_IMPORTS[integration]);
563
569
  const server = kept(SERVER_IMPORTS[integration]);
564
570
  if (app.length > 0) addImports(app);
565
571
  if (server.length > 0) addServerImports(server);
566
572
  }
567
573
  function registerComponents(resolver, integration, autoImports) {
568
- for (const { name, from } of APP_COMPONENTS[integration]) {
569
- if (!isAutoImported(name, autoImports)) continue;
570
- 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 });
571
578
  }
572
579
  }
573
580
  const AUTH_PROXY_SECURITY_RULES = {
@@ -581,7 +588,7 @@ function registerBetterAuth(resolver, authRoute, autoImports) {
581
588
  registerComponents(resolver, "betterAuth", autoImports);
582
589
  addServerHandler({
583
590
  route: `${authRoute}/**`,
584
- handler: resolver.resolve("./runtime/better-auth/nuxt/proxy")
591
+ handler: { nuxt: resolver.resolve("./runtime/better-auth/nuxt/proxy") }
585
592
  });
586
593
  extendRouteRules(`${authRoute}/**`, {
587
594
  cache: false,
@@ -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
  });
@@ -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");
@@ -1,16 +1,15 @@
1
1
  import { computed } from "vue";
2
- import { setResponseHeader } from "h3";
3
- import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent } from "#app";
2
+ import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent, useResponseHeader } from "#app";
4
3
  import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
5
4
  import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
6
5
  import { convexAuth } from "../nuxt/server.js";
7
6
  import { ConvexAuthStateKey } from "../../vue/auth/index.js";
8
- export async function prefetchAuthToken(event, initialToken) {
7
+ export async function prefetchAuthToken(event, initialToken, cacheControl) {
9
8
  try {
10
9
  const token = await convexAuth(event).getToken();
11
10
  initialToken.value = token ?? null;
12
11
  if (token && event) {
13
- setResponseHeader(event, "Cache-Control", "private, no-store");
12
+ cacheControl.value = "private, no-store";
14
13
  }
15
14
  } catch (error) {
16
15
  console.warn("[nuxt-convex-module] Failed to prefetch auth token for SSR:", error);
@@ -33,7 +32,7 @@ export default defineNuxtPlugin({
33
32
  nuxtApp.vueApp.provide(ConvexAuthStateKey, ssrAuthState);
34
33
  const event = useRequestEvent();
35
34
  if (event && !import.meta.prerender) {
36
- await prefetchAuthToken(event, initialToken);
35
+ await prefetchAuthToken(event, initialToken, useResponseHeader("Cache-Control"));
37
36
  }
38
37
  }
39
38
  });
@@ -39,9 +39,6 @@
39
39
  * const messages: AsyncQueryReturn<Doc<'messages'>[]> = useAsyncQuery(api.messages.list, {})
40
40
  * ```
41
41
  *
42
- * Before 1.0 the composable was auto-imported but reachable from no subpath,
43
- * so neither the explicit form nor the types could be written at all.
44
- *
45
42
  * @module app
46
43
  */
47
44
  export { useAsyncQuery, useConvexAsyncQuery, type AsyncQueryData, type AsyncQueryOptions, type AsyncQueryReturn, type AsyncQueryStatus, } from './composables/use-async-query.js';
@@ -5,7 +5,7 @@ export function getConvexRuntimeConfig() {
5
5
  const config = useRuntimeConfig();
6
6
  return {
7
7
  url: config.public?.convex?.url,
8
- siteUrl: config.convex?.siteUrl || config.public?.convex?.siteUrl
8
+ siteUrl: config.public?.convex?.siteUrl
9
9
  };
10
10
  } catch {
11
11
  return {};
@@ -21,7 +21,8 @@ export interface SecurityNitroApp {
21
21
  * origins are appended to whatever the app configured, whatever the order of
22
22
  * `modules`. nuxt-security sorts its own Nitro plugins last, so this listener
23
23
  * is in place before the hook fires. The deployment URL comes from runtime
24
- * config, so it need not be known at build time.
24
+ * config, so it need not be known at build time. `nuxt/server` is its only
25
+ * server import, so the same file runs on Nitro 2 and Nitro 3.
25
26
  *
26
27
  * `connect-src` is tightened only in production: creating it in dev would
27
28
  * block the Vite HMR and devtools WebSockets, which run on ports `'self'`
@@ -1,10 +1,10 @@
1
- import { getConvexRuntimeConfig } from "./config.js";
1
+ import { useRuntimeConfig } from "nuxt/server";
2
2
  import { applyConvexSecurityDefaults, convexConnectSrc, convexResourceSrc } from "./csp.js";
3
3
  export default function convexSecurityPlugin(nitroApp) {
4
4
  nitroApp.hooks.hook("nuxt-security:routeRules", (rules) => {
5
5
  const global = rules["/**"];
6
6
  if (!global) return;
7
- const { url, siteUrl } = getConvexRuntimeConfig();
7
+ const { url, siteUrl } = useRuntimeConfig().public?.convex ?? {};
8
8
  applyConvexSecurityDefaults(
9
9
  global,
10
10
  import.meta.dev ? [] : convexConnectSrc(url, siteUrl),