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.
- package/README.md +26 -47
- package/dist/chunks/index.mjs +72 -18
- 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 +18 -18
- package/dist/module.json +2 -2
- package/dist/module.mjs +101 -66
- package/dist/runtime/auth0/vue/index.js +1 -1
- package/dist/runtime/better-auth/nuxt/middleware.js +2 -13
- 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-guard.d.ts +12 -0
- package/dist/runtime/better-auth/nuxt/server-guard.js +13 -0
- 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/auth-boundary.js +2 -2
- package/dist/runtime/better-auth/vue/client.d.ts +5 -357
- package/dist/runtime/better-auth/vue/plugin.server.js +4 -5
- package/dist/runtime/better-auth/vue/use-better-auth.d.ts +4 -65
- package/dist/runtime/clerk/vue/index.js +1 -1
- package/dist/runtime/nuxt/app.d.ts +0 -3
- package/dist/runtime/nuxt/composables/use-async-paginated-query.js +12 -12
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +2 -2
- package/dist/runtime/nuxt/config.js +1 -1
- package/dist/runtime/nuxt/index.d.ts +6 -6
- 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 +10 -15
- package/dist/runtime/vue/auth/helpers.js +4 -4
- package/dist/runtime/vue/client.d.ts +6 -6
- package/dist/runtime/vue/components/convex-image.js +1 -1
- package/dist/runtime/vue/composables/use-action.d.ts +3 -3
- package/dist/runtime/vue/composables/use-mutation.d.ts +4 -4
- package/dist/runtime/vue/composables/use-queries.d.ts +14 -7
- package/dist/runtime/vue/composables/use-query.d.ts +5 -5
- package/dist/runtime/vue/hydration.d.ts +3 -3
- package/dist/runtime/vue/index.d.ts +1 -2
- package/dist/runtime/vue/index.js +0 -6
- package/dist/runtime/vue/queries-observer.d.ts +9 -9
- package/package.json +43 -41
- package/dist/devtools-client/200.html +0 -1
- package/dist/devtools-client/404.html +0 -1
- package/dist/devtools-client/_nuxt/71bSMC-_.js +0 -2
- package/dist/devtools-client/_nuxt/BAaUCl_m.js +0 -1
- package/dist/devtools-client/_nuxt/BhmjAr2Q.js +0 -1
- package/dist/devtools-client/_nuxt/BmBQNDID.js +0 -1
- package/dist/devtools-client/_nuxt/C6YdTgGl.js +0 -2
- package/dist/devtools-client/_nuxt/CKakiU_h.js +0 -1
- package/dist/devtools-client/_nuxt/Cm7YqZQ-.js +0 -1
- package/dist/devtools-client/_nuxt/DQmVr-EY.js +0 -1
- package/dist/devtools-client/_nuxt/D_Rij7MD.js +0 -1
- package/dist/devtools-client/_nuxt/EZ4-8Xzm.js +0 -3
- package/dist/devtools-client/_nuxt/Kba0XpjH.js +0 -1
- package/dist/devtools-client/_nuxt/OVE7s6ty.js +0 -1
- package/dist/devtools-client/_nuxt/_Lf3mfPR.js +0 -1
- package/dist/devtools-client/_nuxt/builds/latest.json +0 -1
- package/dist/devtools-client/_nuxt/builds/meta/1734dcfa-caf6-447b-a68b-4dd3cf138875.json +0 -1
- package/dist/devtools-client/_nuxt/entry.HF8C6P11.css +0 -1
- package/dist/devtools-client/_nuxt/error-404.CLlwiMp-.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.Bxr6fpDU.css +0 -1
- package/dist/devtools-client/_nuxt/queries.DNBUjWk2.css +0 -1
- package/dist/devtools-client/_nuxt/vZZlEX5A.js +0 -1
- package/dist/devtools-client/auth/index.html +0 -1
- package/dist/devtools-client/logs/index.html +0 -1
- package/dist/devtools-client/queries/index.html +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
|
|
@@ -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
|
|
116
|
-
* the Convex CLI starts the dev deployment,
|
|
117
|
-
*
|
|
118
|
-
* needed for local development. A `dev` script you
|
|
119
|
-
* or one that runs `convex dev` somewhere, is left
|
|
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
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { useLogger, updateTemplates, defineNuxtModule, createResolver, addPlugin, hasNuxtModule,
|
|
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 {
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 = ["
|
|
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
|
|
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
|
|
211
|
-
|
|
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
|
-
//
|
|
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.
|
|
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:
|
|
390
|
+
authRoute: betterAuthOptions.authRoute || "/api/auth",
|
|
357
391
|
loginPath: nuxt.options.runtimeConfig.public.convex.loginPath,
|
|
358
|
-
authClient:
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
494
|
-
filename: "nuxt-convex-module-
|
|
540
|
+
addTypeTemplate({
|
|
541
|
+
filename: "types/nuxt-convex-module-api-fallback.d.ts",
|
|
495
542
|
getContents: () => {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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,
|
|
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 {
|
|
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:
|
|
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
|
});
|
|
@@ -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
|
|
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");
|