nuxt-convex-module 0.11.0 → 0.12.1
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/README.md +9 -47
- package/dist/chunks/index.mjs +56 -24
- package/dist/devtools-client/_nuxt/AqohFEu1.js +1 -0
- package/dist/devtools-client/_nuxt/BPtiIkWa.js +3 -0
- package/dist/devtools-client/_nuxt/DGCFzitC.js +7 -0
- package/dist/devtools-client/_nuxt/entry.egvhSnkx.css +1 -0
- package/dist/devtools-client/index.html +1 -1
- package/dist/module.d.mts +12 -12
- package/dist/module.json +2 -2
- package/dist/module.mjs +65 -58
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +12 -12
- package/dist/runtime/better-auth/nuxt/proxy.js +8 -6
- package/dist/runtime/better-auth/nuxt/server.d.ts +22 -10
- package/dist/runtime/better-auth/nuxt/server.js +11 -4
- package/dist/runtime/better-auth/vue/plugin.server.js +4 -5
- package/dist/runtime/nuxt/app.d.ts +0 -3
- package/dist/runtime/nuxt/config.js +1 -1
- package/dist/runtime/nuxt/security.d.ts +2 -1
- package/dist/runtime/nuxt/security.js +2 -2
- package/dist/runtime/polar/vue/components.d.ts +14 -16
- package/dist/runtime/polar/vue/components.js +8 -13
- package/dist/runtime/vue/index.d.ts +0 -1
- package/dist/runtime/vue/index.js +0 -6
- package/package.json +31 -30
- package/dist/devtools-client/_nuxt/BbiHJLbN.js +0 -4
- package/dist/devtools-client/_nuxt/entry.CfoJPZu4.css +0 -1
- package/dist/runtime/vue/provide.d.ts +0 -39
- 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,
|
|
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
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule,
|
|
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 = ["
|
|
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
|
|
228
|
-
|
|
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
|
-
//
|
|
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.
|
|
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:
|
|
390
|
+
authRoute: betterAuthOptions.authRoute || "/api/auth",
|
|
376
391
|
loginPath: nuxt.options.runtimeConfig.public.convex.loginPath,
|
|
377
|
-
authClient:
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
521
|
-
filename: "nuxt-convex-module-
|
|
540
|
+
addTypeTemplate({
|
|
541
|
+
filename: "types/nuxt-convex-module-api-fallback.d.ts",
|
|
522
542
|
getContents: () => {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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:
|
|
6
|
-
* then the
|
|
7
|
-
*
|
|
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:
|
|
15
|
-
* URL-normalised one. `/api/auth/../../x` (or its `%2e%2e` spelling) matches
|
|
16
|
-
* route and normalises to `/x`, which the proxy would send to the site
|
|
17
|
-
* any HTTP action on the deployment, reached through the app's origin
|
|
18
|
-
* app's cookies attached. Next.js normalises before routing, so
|
|
19
|
-
* handler never sees such a path; Nitro does not, so
|
|
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("
|
|
21
|
+
declare const _default: import("nuxt/server").EventHandler<Promise<Response>>;
|
|
22
22
|
export default _default;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 {
|
|
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
|
|
32
|
-
*
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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 } =
|
|
7
|
+
const { url, siteUrl } = useRuntimeConfig().public?.convex ?? {};
|
|
8
8
|
applyConvexSecurityDefaults(
|
|
9
9
|
global,
|
|
10
10
|
import.meta.dev ? [] : convexConnectSrc(url, siteUrl),
|