nuxt-convex-module 0.0.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +51 -157
  3. package/dist/chunks/index.mjs +1 -1
  4. package/dist/devtools-client/200.html +1 -1
  5. package/dist/devtools-client/404.html +1 -1
  6. package/dist/devtools-client/_nuxt/71bSMC-_.js +2 -0
  7. package/dist/devtools-client/_nuxt/{CZbAGcNC.js → BAaUCl_m.js} +1 -1
  8. package/dist/devtools-client/_nuxt/BhmjAr2Q.js +1 -0
  9. package/dist/devtools-client/_nuxt/BmBQNDID.js +1 -0
  10. package/dist/devtools-client/_nuxt/C6YdTgGl.js +2 -0
  11. package/dist/devtools-client/_nuxt/{DPWXUmcI.js → CKakiU_h.js} +1 -1
  12. package/dist/devtools-client/_nuxt/Cm7YqZQ-.js +1 -0
  13. package/dist/devtools-client/_nuxt/{dy_xXcv9.js → DQmVr-EY.js} +1 -1
  14. package/dist/devtools-client/_nuxt/D_Rij7MD.js +1 -0
  15. package/dist/devtools-client/_nuxt/EZ4-8Xzm.js +3 -0
  16. package/dist/devtools-client/_nuxt/Kba0XpjH.js +1 -0
  17. package/dist/devtools-client/_nuxt/OVE7s6ty.js +1 -0
  18. package/dist/devtools-client/_nuxt/_Lf3mfPR.js +1 -0
  19. package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
  20. package/dist/devtools-client/_nuxt/builds/meta/7dfea6da-3eca-4a35-ba06-5e069ac7e8df.json +1 -0
  21. package/dist/devtools-client/_nuxt/entry.HF8C6P11.css +1 -0
  22. package/dist/devtools-client/_nuxt/error-404.CLlwiMp-.css +1 -0
  23. package/dist/devtools-client/_nuxt/error-500.Bxr6fpDU.css +1 -0
  24. package/dist/devtools-client/_nuxt/queries.DNBUjWk2.css +1 -0
  25. package/dist/devtools-client/_nuxt/vZZlEX5A.js +1 -0
  26. package/dist/devtools-client/auth/index.html +1 -1
  27. package/dist/devtools-client/index.html +1 -1
  28. package/dist/devtools-client/logs/index.html +1 -1
  29. package/dist/devtools-client/queries/index.html +1 -1
  30. package/dist/module.d.mts +129 -0
  31. package/dist/module.json +2 -2
  32. package/dist/module.mjs +576 -0
  33. package/dist/runtime/auth0/vue/index.d.ts +3 -3
  34. package/dist/runtime/better-auth/nuxt/middleware.d.ts +4 -2
  35. package/dist/runtime/better-auth/nuxt/middleware.js +15 -12
  36. package/dist/runtime/better-auth/nuxt/proxy.d.ts +18 -2
  37. package/dist/runtime/better-auth/nuxt/proxy.js +7 -7
  38. package/dist/runtime/better-auth/nuxt/server.d.ts +6 -6
  39. package/dist/runtime/better-auth/nuxt/server.js +15 -0
  40. package/dist/runtime/better-auth/vue/auth-boundary.d.ts +3 -1
  41. package/dist/runtime/better-auth/vue/client.d.ts +1 -1
  42. package/dist/runtime/better-auth/vue/cross-domain.d.ts +4 -5
  43. package/dist/runtime/better-auth/vue/cross-domain.js +11 -4
  44. package/dist/runtime/better-auth/vue/hydration.d.ts +1 -1
  45. package/dist/runtime/better-auth/vue/index.d.ts +2 -1
  46. package/dist/runtime/better-auth/vue/index.js +2 -1
  47. package/dist/runtime/better-auth/vue/plugin.client.d.ts +1 -5
  48. package/dist/runtime/better-auth/vue/plugin.client.js +6 -10
  49. package/dist/runtime/better-auth/vue/plugin.server.d.ts +5 -16
  50. package/dist/runtime/better-auth/vue/plugin.server.js +3 -6
  51. package/dist/runtime/better-auth/vue/redirect.d.ts +33 -0
  52. package/dist/runtime/better-auth/vue/redirect.js +14 -0
  53. package/dist/runtime/better-auth/vue/{use-auth.d.ts → use-better-auth.d.ts} +31 -11
  54. package/dist/runtime/better-auth/vue/{use-auth.js → use-better-auth.js} +4 -3
  55. package/dist/runtime/clerk/vue/index.d.ts +4 -4
  56. package/dist/runtime/devtools/bridge.js +1 -1
  57. package/dist/runtime/nuxt/app.d.ts +48 -0
  58. package/dist/runtime/nuxt/app.js +8 -0
  59. package/dist/runtime/nuxt/composables/use-async-paginated-query.d.ts +88 -0
  60. package/dist/runtime/nuxt/composables/use-async-paginated-query.js +90 -0
  61. package/dist/runtime/nuxt/composables/use-async-query.d.ts +7 -16
  62. package/dist/runtime/nuxt/composables/use-async-query.js +14 -9
  63. package/dist/runtime/nuxt/config.d.ts +11 -0
  64. package/dist/runtime/nuxt/config.js +1 -0
  65. package/dist/runtime/nuxt/csp.d.ts +44 -0
  66. package/dist/runtime/nuxt/csp.js +46 -0
  67. package/dist/runtime/nuxt/index.d.ts +8 -8
  68. package/dist/runtime/nuxt/security.d.ts +33 -0
  69. package/dist/runtime/nuxt/security.js +14 -0
  70. package/dist/runtime/polar/vue/components.d.ts +8 -1
  71. package/dist/runtime/polar/vue/components.js +3 -3
  72. package/dist/runtime/vue/auth/index.d.ts +4 -4
  73. package/dist/runtime/vue/auth/index.js +6 -2
  74. package/dist/runtime/vue/client.d.ts +5 -55
  75. package/dist/runtime/vue/client.js +4 -0
  76. package/dist/runtime/vue/components/convex-image.d.ts +50 -0
  77. package/dist/runtime/vue/components/convex-image.js +21 -0
  78. package/dist/runtime/vue/composables/use-connection-state.d.ts +1 -0
  79. package/dist/runtime/vue/composables/use-paginated-query.d.ts +5 -1
  80. package/dist/runtime/vue/composables/use-paginated-query.js +2 -2
  81. package/dist/runtime/vue/composables/use-query.d.ts +2 -0
  82. package/dist/runtime/vue/composables/use-subscription.d.ts +1 -15
  83. package/dist/runtime/vue/composables/use-upload-queue.d.ts +2 -3
  84. package/dist/runtime/vue/hydration.d.ts +3 -24
  85. package/dist/runtime/vue/index.d.ts +2 -2
  86. package/dist/runtime/vue/index.js +1 -0
  87. package/dist/runtime/vue/plugin.js +1 -1
  88. package/dist/runtime/vue/provide.d.ts +5 -5
  89. package/dist/types.d.mts +3 -0
  90. package/package.json +103 -56
  91. package/dist/devtools-client/_nuxt/B29GGJ5n.js +0 -1
  92. package/dist/devtools-client/_nuxt/BSDEpQet.js +0 -1
  93. package/dist/devtools-client/_nuxt/BY23OQOo.js +0 -1
  94. package/dist/devtools-client/_nuxt/BZYRaL-G.js +0 -1
  95. package/dist/devtools-client/_nuxt/Byw6FxnC.js +0 -1
  96. package/dist/devtools-client/_nuxt/CJJ_hcZN.js +0 -1
  97. package/dist/devtools-client/_nuxt/CS6WMFOx.js +0 -2
  98. package/dist/devtools-client/_nuxt/Cr6_aklh.js +0 -2
  99. package/dist/devtools-client/_nuxt/DNfpyBVU.js +0 -3
  100. package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +0 -1
  101. package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +0 -1
  102. package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +0 -1
  103. package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +0 -1
  104. package/dist/devtools-client/_nuxt/ncgEdbEt.js +0 -1
  105. package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +0 -1
  106. package/dist/module.d.ts +0 -189
  107. package/dist/module.js +0 -489
  108. package/dist/types.d.ts +0 -3
@@ -1,6 +1,22 @@
1
1
  /**
2
- * Proxy all /api/auth/* requests to the Convex site URL.
3
- * This keeps auth cookies on the same origin (no CORS issues).
2
+ * Proxy all `${convex.authRoute}/**` requests (default `/api/auth/**`) to the
3
+ * Convex site URL. This keeps auth cookies on the same origin (no CORS issues).
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.
9
+ *
10
+ * The method list matches the nuxt-security route rule the module registers on this
11
+ * route; enforcing it here too keeps the proxy closed to `PUT`/`DELETE` probes when
12
+ * nuxt-security is not installed.
13
+ *
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.
4
20
  */
5
21
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Response>>;
6
22
  export default _default;
@@ -1,11 +1,11 @@
1
- import { defineEventHandler } from "h3";
2
- import { useRuntimeConfig } from "#imports";
1
+ import { assertMethod, createError, defineEventHandler, toWebRequest } from "h3";
3
2
  import { convexAuth } from "./server.js";
4
3
  export default defineEventHandler(async (event) => {
5
- const config = useRuntimeConfig();
6
- const siteUrl = config.convex.siteUrl;
7
- if (!siteUrl) {
8
- throw new Error("[nuxt-convex-module] NUXT_PUBLIC_CONVEX_SITE_URL is not configured. Auth proxy cannot forward requests.");
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" });
9
9
  }
10
- return convexAuth(event, { convexSiteUrl: siteUrl }).handler();
10
+ return convexAuth(event).handler();
11
11
  });
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * Server-side Better Auth + Convex integration for Nuxt.
3
3
  *
4
- * Provides {@link convexAuth} for Nuxt server handlers.
5
- *
6
- * It accepts the H3
7
- * {@link H3Event} that is available in every Nuxt server route, API handler
8
- * and server middleware.
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.
9
7
  *
10
8
  * @module better-auth/server
11
9
  */
@@ -30,7 +28,9 @@ export type ConvexAuthOptions = GetTokenOptions & {
30
28
  */
31
29
  convexUrl?: string;
32
30
  /**
33
- * Override the Convex site URL. Defaults to `NUXT_PUBLIC_CONVEX_SITE_URL`.
31
+ * Override the Convex site URL. Defaults to `runtimeConfig.convex.siteUrl`,
32
+ * then `runtimeConfig.public.convex.siteUrl`, then the
33
+ * `NUXT_PUBLIC_CONVEX_SITE_URL` process variable.
34
34
  */
35
35
  convexSiteUrl?: string;
36
36
  };
@@ -11,6 +11,21 @@ const parseConvexSiteUrl = (url) => {
11
11
  if (url.endsWith(".convex.cloud")) {
12
12
  throw new Error(
13
13
  `NUXT_PUBLIC_CONVEX_SITE_URL should be set to your Convex Site URL, which ends in .convex.site.
14
+ Currently set to ${url}.`
15
+ );
16
+ }
17
+ let protocol;
18
+ try {
19
+ protocol = new URL(url).protocol;
20
+ } catch {
21
+ throw new Error(
22
+ `NUXT_PUBLIC_CONVEX_SITE_URL must be an absolute URL, including the scheme.
23
+ Currently set to ${url}.`
24
+ );
25
+ }
26
+ if (protocol !== "https:" && protocol !== "http:") {
27
+ throw new Error(
28
+ `NUXT_PUBLIC_CONVEX_SITE_URL must use http: or https:.
14
29
  Currently set to ${url}.`
15
30
  );
16
31
  }
@@ -1,7 +1,7 @@
1
1
  import { type PropType, type VNode } from 'vue';
2
2
  import type { FunctionReference } from 'convex/server';
3
- import type { EmptyObject } from 'convex-helpers';
4
3
  import type { AuthClient } from '#convex/auth-client';
4
+ type EmptyObject = Record<string, never>;
5
5
  /**
6
6
  * _Experimental_
7
7
  *
@@ -43,6 +43,7 @@ import type { AuthClient } from '#convex/auth-client';
43
43
  * @param props.isAuthError - Function to check if the error is auth related.
44
44
  *
45
45
  * @public
46
+ * @experimental May change in a minor release — see STABILITY.md.
46
47
  */
47
48
  export declare const AuthBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
48
49
  /**
@@ -129,3 +130,4 @@ export declare const AuthBoundary: import("vue").DefineComponent<import("vue").E
129
130
  }>> & Readonly<{}>, {
130
131
  renderFallback: () => VNode | null;
131
132
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
133
+ export {};
@@ -172,7 +172,7 @@ export declare const authClient: import("better-auth/vue").VueAuthClient<{
172
172
  metadata: {
173
173
  isAction: false;
174
174
  };
175
- }, import("better-auth/plugins").OIDCMetadata>;
175
+ }, import("better-auth/plugins/oidc-provider").OIDCMetadata>;
176
176
  getJwks: import("better-auth").StrictEndpoint<"/convex/jwks", {
177
177
  method: "GET";
178
178
  metadata: {
@@ -11,11 +11,10 @@ export interface ConsumeCrossDomainOneTimeTokenOptions {
11
11
  *
12
12
  * The cross-domain protocol cannot bind the token to the browser that
13
13
  * started the sign-in flow (magic-link flows legitimately finish in another
14
- * browsing context), so without this restriction a crafted
15
- * `/any-page?ott=…` link silently signs the visitor into the link author's
16
- * session — login CSRF. Restricting consumption to the one route your
17
- * sign-in `callbackURL`s point at shrinks that surface to a single
18
- * predictable URL.
14
+ * browsing context), so without this restriction a token completes sign-in
15
+ * on whatever page receives it. Restricting consumption to the one route
16
+ * your sign-in `callbackURL`s point at keeps that to a single predictable
17
+ * URL.
19
18
  *
20
19
  * In Nuxt, set `convex.betterAuth.crossDomainCallbackRoute` instead — the
21
20
  * client plugin forwards it here.
@@ -1,7 +1,9 @@
1
1
  import { authClient } from "#convex/auth-client";
2
2
  const normalizePathname = (path) => {
3
3
  const slashed = path.startsWith("/") ? path : `/${path}`;
4
- const trimmed = slashed.replace(/\/+$/, "");
4
+ let end = slashed.length;
5
+ while (end > 0 && slashed[end - 1] === "/") end--;
6
+ const trimmed = slashed.slice(0, end);
5
7
  return trimmed === "" ? "/" : trimmed;
6
8
  };
7
9
  export async function consumeCrossDomainOneTimeToken(options = {}) {
@@ -19,10 +21,15 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
19
21
  }
20
22
  if (options.callbackRoute && normalizePathname(url.pathname) !== normalizePathname(options.callbackRoute)) {
21
23
  console.warn(
22
- `[nuxt-convex-module] ignoring cross-domain one-time token on "${url.pathname}" \u2014 consumption is restricted to "${options.callbackRoute}" (convex.betterAuth.crossDomainCallbackRoute).`
24
+ `[nuxt-convex-module] Ignoring cross-domain one-time token on "${url.pathname}" \u2014 consumption is restricted to "${options.callbackRoute}" (convex.betterAuth.crossDomainCallbackRoute).`
23
25
  );
24
26
  return;
25
27
  }
28
+ if (import.meta.dev && !options.callbackRoute) {
29
+ console.warn(
30
+ `[nuxt-convex-module] Exchanged a cross-domain one-time token on "${url.pathname}" with no \`convex.betterAuth.crossDomainCallbackRoute\` set \u2014 any page can complete sign-in. Restrict it to your callback route.`
31
+ );
32
+ }
26
33
  try {
27
34
  const result = await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
28
35
  token
@@ -36,10 +43,10 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
36
43
  }
37
44
  }
38
45
  });
39
- authClientWithCrossDomain.updateSession();
46
+ await authClientWithCrossDomain.updateSession();
40
47
  }
41
48
  } catch (error) {
42
- console.warn("[nuxt-convex-module] failed to consume cross-domain one-time token", error);
49
+ console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", error);
43
50
  }
44
51
  }
45
52
  }
@@ -2,7 +2,7 @@ import type { FunctionReference } from 'convex/server';
2
2
  import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
3
  import { type Preloaded } from '../../vue/hydration.js';
4
4
  /**
5
- * Auth-aware version of {@link usePreloadedQuery} for payloads returned by
5
+ * Auth-aware version of {@link client!usePreloadedQuery | usePreloadedQuery} for payloads returned by
6
6
  * `convexAuth(event).preloadAuthQuery(...)`.
7
7
  *
8
8
  * A Vue/Nuxt port of `@convex-dev/better-auth`'s `usePreloadedAuthQuery`
@@ -4,9 +4,10 @@
4
4
  *
5
5
  * @module better-auth/client
6
6
  */
7
- export { useAuth, type UseAuthService, type AuthSession, type AuthUser } from './use-auth.js';
7
+ export { useBetterAuth, type UseBetterAuthReturn, type BetterAuthSession, type BetterAuthUser } from './use-better-auth.js';
8
8
  export { authClient, type AuthClient } from '#convex/auth-client';
9
9
  export { usePreloadedAuthQuery } from './hydration.js';
10
10
  export { consumeCrossDomainOneTimeToken, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
11
+ export { resolveAuthRedirect } from './redirect.js';
11
12
  export { AuthBoundary } from './auth-boundary.js';
12
13
  export { convexClient, crossDomainClient } from '@convex-dev/better-auth/client/plugins';
@@ -1,6 +1,7 @@
1
- export { useAuth } from "./use-auth.js";
1
+ export { useBetterAuth } from "./use-better-auth.js";
2
2
  export { authClient } from "#convex/auth-client";
3
3
  export { usePreloadedAuthQuery } from "./hydration.js";
4
4
  export { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
5
+ export { resolveAuthRedirect } from "./redirect.js";
5
6
  export { AuthBoundary } from "./auth-boundary.js";
6
7
  export { convexClient, crossDomainClient } from "@convex-dev/better-auth/client/plugins";
@@ -1,6 +1,2 @@
1
- import { ConvexVueClient } from '../../vue/client.js';
2
- type ConvexNuxtInjection = {
3
- convex?: ConvexVueClient;
4
- };
5
- declare const _default: import("nuxt/app").Plugin<ConvexNuxtInjection> & import("nuxt/app").ObjectPlugin<ConvexNuxtInjection>;
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
6
2
  export default _default;
@@ -1,32 +1,28 @@
1
1
  import { defineNuxtPlugin, useRuntimeConfig, useState } from "#app";
2
2
  import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
3
3
  import { ConvexAuthStateKey, createScopedConvexAuthState } from "../../vue/auth/index.js";
4
- import { useAuth } from "./use-auth.js";
4
+ import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
5
+ import { useBetterAuth } from "./use-better-auth.js";
5
6
  import { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
6
7
  export default defineNuxtPlugin({
7
8
  name: "nuxt-convex-module:better-auth:client",
8
9
  async setup(nuxtApp) {
9
10
  const { url, crossDomainCallbackRoute } = useRuntimeConfig().public.convex;
10
11
  if (!url) {
11
- console.warn("[nuxt-convex-module] No Convex URL configured for client plugin.");
12
- return { provide: {} };
12
+ console.warn("[nuxt-convex-module] No Convex deployment URL configured \u2014 client and auth not created. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.");
13
+ return;
13
14
  }
14
15
  const client = new ConvexVueClient(url);
15
16
  nuxtApp.vueApp.provide(ConvexClientKey, client);
16
17
  await consumeCrossDomainOneTimeToken({
17
18
  callbackRoute: crossDomainCallbackRoute || void 0
18
19
  });
19
- const initialToken = useState("convex:initialToken", () => null);
20
+ const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
20
21
  const { state, scope } = createScopedConvexAuthState({
21
22
  client,
22
- useAuth: () => useAuth(initialToken.value)
23
+ useAuth: () => useBetterAuth(initialToken.value)
23
24
  });
24
25
  nuxtApp.vueApp.provide(ConvexAuthStateKey, state);
25
26
  void scope;
26
- return {
27
- provide: {
28
- convex: client
29
- }
30
- };
31
27
  }
32
28
  });
@@ -1,24 +1,13 @@
1
- import { useState, useRequestEvent } from '#app';
2
- import { ConvexVueClient } from '../../vue/client.js';
3
- type ConvexNuxtInjection = {
4
- convex?: ConvexVueClient;
5
- };
6
- /**
7
- * Prefetch the Convex JWT for SSR and stash it in `initialToken`.
8
- *
9
- * Exported for unit testing.
10
- */
11
- export declare function prefetchAuthToken(event: ReturnType<typeof useRequestEvent>, initialToken: ReturnType<typeof useState<string | null>>): Promise<void>;
12
1
  /**
13
2
  * Server-side Better Auth bootstrap.
14
3
  *
15
4
  * Mirrors the Next.js parity layer that calls `getToken()` from the root
16
5
  * server layout and passes it as `initialToken` to `ConvexBetterAuthProvider`.
17
6
  *
18
- * The token is stashed into a Nuxt `useState('convex:initialToken')` so the
19
- * client plugin can hand it to `useAuth(initialToken)` before the first
20
- * Convex `setAuth` call — avoiding an extra Better Auth round-trip on first
21
- * paint.
7
+ * The token is stashed into a Nuxt `useState` (`CONVEX_INITIAL_TOKEN_KEY`) so
8
+ * the client plugin can hand it to `useBetterAuth(initialToken)` before the
9
+ * first Convex `setAuth` call — avoiding an extra Better Auth round-trip on
10
+ * first paint.
22
11
  */
23
- declare const _default: import("nuxt/app").Plugin<ConvexNuxtInjection> & import("nuxt/app").ObjectPlugin<ConvexNuxtInjection>;
12
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
24
13
  export default _default;
@@ -2,11 +2,9 @@ import { computed } from "vue";
2
2
  import { setResponseHeader } from "h3";
3
3
  import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent } from "#app";
4
4
  import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
5
+ import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
5
6
  import { convexAuth } from "../nuxt/server.js";
6
7
  import { ConvexAuthStateKey } from "../../vue/auth/index.js";
7
- function buildProvide(ssrClient) {
8
- return ssrClient ? { provide: { convex: ssrClient } } : { provide: {} };
9
- }
10
8
  export async function prefetchAuthToken(event, initialToken) {
11
9
  try {
12
10
  const token = await convexAuth(event).getToken();
@@ -21,7 +19,7 @@ export async function prefetchAuthToken(event, initialToken) {
21
19
  export default defineNuxtPlugin({
22
20
  name: "nuxt-convex-module:better-auth:server",
23
21
  async setup(nuxtApp) {
24
- const initialToken = useState("convex:initialToken", () => null);
22
+ const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
25
23
  const url = useRuntimeConfig().public.convex.url;
26
24
  const ssrClient = url ? new ConvexVueClient(url) : void 0;
27
25
  if (ssrClient) {
@@ -34,9 +32,8 @@ export default defineNuxtPlugin({
34
32
  };
35
33
  nuxtApp.vueApp.provide(ConvexAuthStateKey, ssrAuthState);
36
34
  const event = useRequestEvent();
37
- if (event) {
35
+ if (event && !import.meta.prerender) {
38
36
  await prefetchAuthToken(event, initialToken);
39
37
  }
40
- return buildProvide(ssrClient);
41
38
  }
42
39
  });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Validate a post-sign-in redirect destination, returning `fallback` unless it
3
+ * is a same-origin path.
4
+ *
5
+ * Use it wherever a login page consumes the `?redirect=` query the `auth`
6
+ * middleware sets — never navigate to that value directly.
7
+ *
8
+ * @example
9
+ * ```vue
10
+ * <script setup lang="ts">
11
+ * const route = useRoute()
12
+ * const { client } = useBetterAuth()
13
+ *
14
+ * async function onSubmit() {
15
+ * await client.signIn.email({ email, password })
16
+ * // '/dashboard' survives; 'https://evil.example' becomes '/'.
17
+ * await navigateTo(resolveAuthRedirect(route.query.redirect))
18
+ * }
19
+ * </script>
20
+ * ```
21
+ *
22
+ * @param value - Candidate destination, typically `route.query.redirect`. A
23
+ * repeated query parameter arrives as an array and is rejected: which entry
24
+ * the app meant is ambiguous, and an attacker chooses the other one.
25
+ * @param fallback - Where to send the visitor when `value` is not a safe
26
+ * same-origin path. Defaults to `/`, and is returned as given — pass a path
27
+ * you control, not user input.
28
+ *
29
+ * @returns The normalized `pathname + search + hash`, or `fallback`.
30
+ *
31
+ * @public
32
+ */
33
+ export declare function resolveAuthRedirect(value: unknown, fallback?: string): string;
@@ -0,0 +1,14 @@
1
+ const SENTINEL_ORIGIN = "http://redirect.invalid";
2
+ export function resolveAuthRedirect(value, fallback = "/") {
3
+ if (typeof value !== "string" || value === "") return fallback;
4
+ if (!value.startsWith("/")) return fallback;
5
+ try {
6
+ const url = new URL(value, SENTINEL_ORIGIN);
7
+ if (url.origin !== SENTINEL_ORIGIN) return fallback;
8
+ const path = `${url.pathname}${url.search}${url.hash}`;
9
+ if (new URL(path, SENTINEL_ORIGIN).origin !== SENTINEL_ORIGIN) return fallback;
10
+ return path;
11
+ } catch {
12
+ return fallback;
13
+ }
14
+ }
@@ -27,7 +27,9 @@ declare const useClientSession: () => Readonly<import("vue").Ref<{
27
27
  readonly isPending: boolean;
28
28
  readonly isRefetching: boolean;
29
29
  readonly error: import("better-auth/client").BetterFetchError | null;
30
- readonly refetch: (queryParams?: {
30
+ readonly refetch: (queryParams
31
+ /** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
32
+ ?: {
31
33
  query?: import("better-auth").SessionQueryParams;
32
34
  } | undefined) => Promise<void>;
33
35
  }, {
@@ -56,37 +58,55 @@ declare const useClientSession: () => Readonly<import("vue").Ref<{
56
58
  readonly isPending: boolean;
57
59
  readonly isRefetching: boolean;
58
60
  readonly error: import("better-auth/client").BetterFetchError | null;
59
- readonly refetch: (queryParams?: {
61
+ readonly refetch: (queryParams
62
+ /** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
63
+ ?: {
60
64
  query?: import("better-auth").SessionQueryParams;
61
65
  } | undefined) => Promise<void>;
62
66
  }>>;
63
- export type AuthSession = ReturnType<typeof useClientSession>;
67
+ export type BetterAuthSession = ReturnType<typeof useClientSession>;
64
68
  /** The signed-in user (loose — exact fields depend on your auth schema). */
65
- export type AuthUser = {
69
+ export type BetterAuthUser = {
66
70
  id: string;
67
71
  email: string;
68
72
  name: string;
69
73
  } & Record<string, unknown>;
70
- export interface UseAuthService {
74
+ /** What `useBetterAuth()` returns. */
75
+ export interface UseBetterAuthReturn {
76
+ /** `true` until the session is known and no prefetched token covers the gap. */
71
77
  isLoading: ComputedRef<boolean>;
78
+ /** `true` when Better Auth reports a session, or a token is cached while it reloads. */
72
79
  isAuthenticated: ComputedRef<boolean>;
80
+ /** Convex's token fetcher: the Better Auth JWT for the current session, or `null`. */
73
81
  fetchAccessToken: AuthTokenFetcher;
82
+ /** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
74
83
  client: AuthClient;
75
- session: AuthSession;
84
+ /** Better Auth's own `useSession()` ref: `{ data, isPending, error }`. */
85
+ session: BetterAuthSession;
76
86
  /** The current user, or `null` when signed out / still loading. */
77
- user: ComputedRef<AuthUser | null>;
87
+ user: ComputedRef<BetterAuthUser | null>;
88
+ /** The session id (or user id); changes when the signed-in identity changes. */
78
89
  authVersion: ComputedRef<string | null>;
79
90
  }
80
91
  /**
81
- * Unified Better Auth service for the Vue/Nuxt runtime.
92
+ * The Better Auth service for the Vue/Nuxt runtime.
82
93
  *
83
94
  * Returns the full Better Auth client, the reactive session wrapper, and the
84
95
  * Convex-compatible auth state used by the packaged auth plugin.
85
96
  *
86
97
  * @param initialToken - Optional preloaded token, used once per app lifetime
87
98
  * to avoid a round-trip on initial load (e.g. from SSR).
99
+ *
100
+ * @example
101
+ * ```vue
102
+ * <script setup lang="ts">
103
+ * const { user, client, isAuthenticated } = useBetterAuth()
104
+ *
105
+ * const signOut = () => client.signOut()
106
+ * </script>
107
+ * ```
108
+ *
109
+ * @public
88
110
  */
89
- export declare function useAuth(initialToken?: string | null): UseAuthService;
90
- /** Reset module-level token cache — intended for tests only. */
91
- export declare function __resetUseAuthForTests(): void;
111
+ export declare function useBetterAuth(initialToken?: string | null): UseBetterAuthReturn;
92
112
  export {};
@@ -7,7 +7,7 @@ let pendingTokenVersion = null;
7
7
  let initialTokenUsed = false;
8
8
  const PENDING_AUTH_VERSION = "__pending__";
9
9
  const useClientSession = () => authClient.useSession();
10
- export function useAuth(initialToken) {
10
+ export function useBetterAuth(initialToken) {
11
11
  if (!initialTokenUsed && initialToken) {
12
12
  cachedToken.value = initialToken;
13
13
  cachedTokenVersion = PENDING_AUTH_VERSION;
@@ -55,6 +55,7 @@ export function useAuth(initialToken) {
55
55
  };
56
56
  return {
57
57
  isLoading: computed(() => session.value.isPending && !cachedToken.value),
58
+ // PARITY: D-03
58
59
  // Diverges from upstream's `Boolean(session?.session) || cachedToken !== null`:
59
60
  // a settled signed-out session must read unauthenticated immediately (Vue has
60
61
  // no re-render to run upstream's cache-clearing effect first), and Better Auth
@@ -63,7 +64,7 @@ export function useAuth(initialToken) {
63
64
  () => !!session.value.data || session.value.isPending && cachedToken.value !== null
64
65
  ),
65
66
  fetchAccessToken,
66
- // Vue-only service extensions (documented in PARITY.md).
67
+ // PARITY: A-06 — Vue-only service extensions.
67
68
  client,
68
69
  session,
69
70
  user: computed(() => {
@@ -73,7 +74,7 @@ export function useAuth(initialToken) {
73
74
  authVersion
74
75
  };
75
76
  }
76
- export function __resetUseAuthForTests() {
77
+ export function __resetUseBetterAuthForTests() {
77
78
  cachedToken.value = null;
78
79
  cachedTokenVersion = null;
79
80
  pendingToken = null;
@@ -16,15 +16,15 @@ export type UseAuth = () => {
16
16
  * Options for {@link provideConvexAuthFromClerk} / `<ConvexProviderWithClerk>`.
17
17
  */
18
18
  export interface ConvexProviderWithClerkOptions {
19
- /** Convex client to authenticate. Defaults to the provided {@link useConvex} client. */
19
+ /** Convex client to authenticate. Defaults to the client `useConvex()` returns. */
20
20
  client?: IConvexVueClient;
21
21
  /** Clerk's `useAuth` composable. Defaults to `useAuth` from `@clerk/vue`. */
22
22
  useAuth?: UseAuth;
23
23
  }
24
24
  /**
25
- * A composable which provides a {@link ConvexVueClient}
25
+ * A composable which provides a {@link client!ConvexVueClient | ConvexVueClient}
26
26
  * authenticated with Clerk, exposing the reactive auth state to descendants
27
- * via {@link useConvexAuth} — the Vue translation of wrapping the app in
27
+ * via {@link client!useConvexAuth | useConvexAuth} — the Vue translation of wrapping the app in
28
28
  * `convex/react-clerk`'s `<ConvexProviderWithClerk>`.
29
29
  *
30
30
  * Call it in a top-level component's `setup`. The app must be wrapped by a
@@ -47,7 +47,7 @@ export interface ConvexProviderWithClerkOptions {
47
47
  */
48
48
  export declare function provideConvexAuthFromClerk(options?: ConvexProviderWithClerkOptions): ConvexAuthState;
49
49
  /**
50
- * A wrapper Vue component which provides a {@link ConvexVueClient}
50
+ * A wrapper Vue component which provides a {@link client!ConvexVueClient | ConvexVueClient}
51
51
  * authenticated with Clerk — the component form of
52
52
  * {@link provideConvexAuthFromClerk}, kept as a drop-in parity port of
53
53
  * `convex/react-clerk`'s `<ConvexProviderWithClerk>`. Renders its default
@@ -17,7 +17,7 @@ function toPlainJson(value) {
17
17
  try {
18
18
  return convexToJson(value);
19
19
  } catch {
20
- return String(value);
20
+ return typeof value === "object" && value !== null ? `[unserializable ${Array.isArray(value) ? "array" : "object"}]` : String(value);
21
21
  }
22
22
  }
23
23
  function renderLogArg(arg) {
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Composables that need the Nuxt app context (`#app`) — the half of the
3
+ * package that runs in a Nuxt app but not in plain Vue.
4
+ *
5
+ * This module contains {@link useAsyncQuery} and its paginated form
6
+ * {@link useAsyncPaginatedQuery}: server-rendered Convex data that upgrades
7
+ * to a live subscription after hydration.
8
+ *
9
+ * ## Usage
10
+ *
11
+ * **Default — auto-imported.** In any page, component or composable, call it
12
+ * with no import; its types are auto-imported too:
13
+ *
14
+ * ```vue
15
+ * <script setup lang="ts">
16
+ * import { api } from '#convex/api'
17
+ *
18
+ * const { data: messages, status } = useAsyncQuery(api.messages.list, {})
19
+ * // `AsyncQueryStatus`, `AsyncQueryReturn`, … resolve without an import as well:
20
+ * const label = (s: AsyncQueryStatus) => s === 'pending' ? 'Loading…' : ''
21
+ * </script>
22
+ * ```
23
+ *
24
+ * **Explicit import — this subpath.** For a project that turns auto-imports
25
+ * off, a file outside the app's auto-import scope, or an editor that wants
26
+ * the import spelled out:
27
+ *
28
+ * ```ts
29
+ * import { useAsyncQuery } from 'nuxt-convex-module/app'
30
+ * ```
31
+ *
32
+ * **Types.** From `#imports` (Nuxt's auto-import barrel) or from this subpath,
33
+ * whichever your file already uses:
34
+ *
35
+ * ```ts
36
+ * import type { AsyncQueryReturn } from '#imports'
37
+ * import type { AsyncQueryReturn } from 'nuxt-convex-module/app'
38
+ *
39
+ * const messages: AsyncQueryReturn<Doc<'messages'>[]> = useAsyncQuery(api.messages.list, {})
40
+ * ```
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
+ * @module app
46
+ */
47
+ export { useAsyncQuery, useConvexAsyncQuery, type AsyncQueryData, type AsyncQueryOptions, type AsyncQueryReturn, type AsyncQueryStatus, } from './composables/use-async-query.js';
48
+ export { useAsyncPaginatedQuery, useConvexAsyncPaginatedQuery, type AsyncPaginatedQueryData, type AsyncPaginatedQueryOptions, type AsyncPaginatedQueryReturn, } from './composables/use-async-paginated-query.js';
@@ -0,0 +1,8 @@
1
+ export {
2
+ useAsyncQuery,
3
+ useConvexAsyncQuery
4
+ } from "./composables/use-async-query.js";
5
+ export {
6
+ useAsyncPaginatedQuery,
7
+ useConvexAsyncPaginatedQuery
8
+ } from "./composables/use-async-paginated-query.js";