nuxt-convex-module 0.13.0 → 0.14.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 (30) hide show
  1. package/dist/devtools-client/index.html +1 -1
  2. package/dist/module.d.mts +18 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +120 -36
  5. package/dist/runtime/better-auth/nuxt/middleware.js +8 -2
  6. package/dist/runtime/better-auth/nuxt/no-session-vary.d.ts +38 -0
  7. package/dist/runtime/better-auth/nuxt/no-session-vary.js +21 -0
  8. package/dist/runtime/better-auth/nuxt/server.js +2 -1
  9. package/dist/runtime/better-auth/nuxt/shared-render.d.ts +9 -0
  10. package/dist/runtime/better-auth/nuxt/shared-render.js +17 -0
  11. package/dist/runtime/better-auth/vue/auth-client-loader.d.ts +17 -0
  12. package/dist/runtime/better-auth/vue/auth-client-loader.js +186 -0
  13. package/dist/runtime/better-auth/vue/cross-domain.d.ts +2 -2
  14. package/dist/runtime/better-auth/vue/cross-domain.js +52 -35
  15. package/dist/runtime/better-auth/vue/index.d.ts +1 -0
  16. package/dist/runtime/better-auth/vue/index.js +1 -0
  17. package/dist/runtime/better-auth/vue/plugin.client.js +21 -2
  18. package/dist/runtime/better-auth/vue/plugin.server.js +23 -3
  19. package/dist/runtime/better-auth/vue/token-store.js +4 -0
  20. package/dist/runtime/better-auth/vue/use-better-auth.d.ts +7 -1
  21. package/dist/runtime/better-auth/vue/use-better-auth.js +78 -6
  22. package/dist/runtime/nuxt/function-name.d.ts +9 -0
  23. package/dist/runtime/nuxt/function-name.js +38 -0
  24. package/dist/runtime/nuxt/index.js +4 -3
  25. package/dist/runtime/nuxt/testing.d.ts +21 -0
  26. package/dist/runtime/nuxt/testing.js +1 -0
  27. package/dist/runtime/vue/composables/use-upload-queue.d.ts +70 -3
  28. package/dist/runtime/vue/composables/use-upload-queue.js +74 -19
  29. package/dist/runtime/vue/index.d.ts +1 -1
  30. package/package.json +8 -1
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/CtC_CmOY.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.jOWrhpYd.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/CtC_CmOY.js"><script type="module" src="/__nuxt-convex-module/_nuxt/CtC_CmOY.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/C-J9qZjr.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"ad777e0e-9095-4e0e-9b9b-8010fe4e34c6",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794085598149,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/CtC_CmOY.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.jOWrhpYd.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/CtC_CmOY.js"><script type="module" src="/__nuxt-convex-module/_nuxt/CtC_CmOY.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/C-J9qZjr.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"168fd639-55a2-4ae2-9c90-a285b0720204",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794180177669,false]</script></body></html>
package/dist/module.d.mts CHANGED
@@ -2,6 +2,8 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  /** The `convex.autoImports` option: every name, every name prefixed with `Convex`, or none. */
4
4
  type AutoImports = boolean | 'prefixed';
5
+ /** The `convex.exclude` option: registered names to skip, each exact or a pattern. */
6
+ type ExcludedNames = ReadonlyArray<string | RegExp>;
5
7
 
6
8
  /**
7
9
  * Configuration for the opt-in Better Auth integration.
@@ -108,6 +110,20 @@ interface ModuleOptions {
108
110
  * the subpath exports. `false` auto-imports nothing.
109
111
  */
110
112
  autoImports?: AutoImports;
113
+ /**
114
+ * Names to leave unregistered, matched against the name the app would see
115
+ * (after `prefixed` renames it): app auto-imports, Nitro auto-imports,
116
+ * components and the `convex-auth` route middleware. A string matches one
117
+ * name exactly, a `RegExp` every name it matches. Everything stays
118
+ * importable from the subpath exports. Meant for a module that builds on
119
+ * this one and registers names of its own for the same things.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * convex: { exclude: [/^useConvex/, 'ConvexImage', 'convex-auth'] }
124
+ * ```
125
+ */
126
+ exclude?: ExcludedNames;
111
127
  /**
112
128
  * Convex panel in Nuxt DevTools (dev only): connection state, live query
113
129
  * subscriptions, auth state, and client logs. Enabled by default whenever
@@ -134,6 +150,8 @@ declare module '@nuxt/schema' {
134
150
  crossDomainCallbackRoute: string;
135
151
  loginPath: string;
136
152
  authRoute: string;
153
+ /** Whether the browser may hold the Better Auth client back (PARITY D-18): only where the registration transform runs, on Vite. */
154
+ deferAuthClient: boolean;
137
155
  };
138
156
  }
139
157
  }
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.6.0"
7
7
  },
8
- "version": "0.13.0",
8
+ "version": "0.14.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { useLogger, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addNitroPlugin, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
2
- import { dirname, join, isAbsolute, basename } from 'node:path';
1
+ import { useLogger, addVitePlugin, resolvePath, defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addNitroPlugin, addTypeTemplate, addImports, addServerImports, addComponent, addServerHandler, extendRouteRules, addRouteMiddleware } from '@nuxt/kit';
2
+ import path, { dirname, join, isAbsolute, basename } from 'node:path';
3
3
  import { detectAgent } from 'std-env';
4
- import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
4
+ import { existsSync, readFileSync, statSync, realpathSync, writeFileSync } from 'node:fs';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { parseEnv } from 'node:util';
7
7
 
@@ -206,6 +206,71 @@ function getConvexAliases(rootDir) {
206
206
  };
207
207
  }
208
208
 
209
+ const SCRIPT_EXTENSION = /\.(?:[cm]?[jt]sx?|vue)$/;
210
+ const posix = (file) => file.replaceAll("\\", "/");
211
+ function sameModule(file, target) {
212
+ if (SCRIPT_EXTENSION.test(target)) return posix(file) === posix(target);
213
+ return posix(file).replace(SCRIPT_EXTENSION, "") === posix(target);
214
+ }
215
+ function manifestKeyMatches(key, root, target, paths = path) {
216
+ return sameModule(key, paths.relative(root, target));
217
+ }
218
+ function real(file) {
219
+ try {
220
+ return realpathSync(file);
221
+ } catch {
222
+ return file;
223
+ }
224
+ }
225
+ function authClientFiles(nuxt) {
226
+ let files;
227
+ return () => files ??= (async () => {
228
+ const target = nuxt.options.alias["#convex/auth-client"];
229
+ if (!target) return [];
230
+ const resolved = await resolvePath(target, { fallbackToOriginal: true });
231
+ return [...new Set(resolved === target ? [target, real(target)] : [resolved, real(resolved)])];
232
+ })();
233
+ }
234
+ function authClientRegistration(file, loader) {
235
+ return [
236
+ "",
237
+ "// nuxt-convex-module (PARITY D-18): tell the loader this client has evaluated.",
238
+ `import { registerEvaluatedAuthClient as __convexRegisterAuthClient } from ${JSON.stringify(loader)}`,
239
+ `import * as __convexAuthClientModule from ${JSON.stringify(`./${basename(file)}`)}`,
240
+ "__convexRegisterAuthClient(__convexAuthClientModule.authClient)",
241
+ ""
242
+ ].join("\n");
243
+ }
244
+ function registerAuthClientOnEvaluation(resolver, nuxt) {
245
+ const loader = resolver.resolve("./runtime/better-auth/vue/auth-client-loader");
246
+ const files = authClientFiles(nuxt);
247
+ addVitePlugin({
248
+ name: "nuxt-convex-module:auth-client-registration",
249
+ async transform(code, id, transformOptions) {
250
+ if (transformOptions?.ssr) return;
251
+ const file = id.split("?")[0];
252
+ if (!(await files()).some((target) => sameModule(file, target))) return;
253
+ return { code: code + authClientRegistration(file, loader), map: null };
254
+ }
255
+ }, { server: false });
256
+ }
257
+ function keepAuthClientOffPrefetch(nuxt) {
258
+ const files = authClientFiles(nuxt);
259
+ nuxt.hook("build:manifest", async (manifest) => {
260
+ const targets = await files();
261
+ if (targets.length === 0) return;
262
+ const root = nuxt.options.vite?.root ?? nuxt.options.srcDir;
263
+ const roots = [.../* @__PURE__ */ new Set([root, real(root)])];
264
+ const isAuthClient = (key) => roots.some((base) => targets.some((target) => manifestKeyMatches(manifest[key]?.src ?? key, base, target)));
265
+ const keys = new Set(Object.keys(manifest).filter(isAuthClient));
266
+ if (keys.size === 0) return;
267
+ for (const chunk of Object.values(manifest)) {
268
+ chunk.dynamicImports &&= chunk.dynamicImports.filter((key) => !keys.has(key));
269
+ }
270
+ for (const key of keys) manifest[key].prefetch = false;
271
+ });
272
+ }
273
+
209
274
  function watchConvexCodegen(nuxt) {
210
275
  if (!nuxt.options.dev) return;
211
276
  const generatedDir = join(nuxt.options.rootDir, resolveFunctionsDir(nuxt.options.rootDir), "_generated");
@@ -253,6 +318,17 @@ function autoImportedAs(name, mode, registered) {
253
318
  const prefixed = prefixedName(name);
254
319
  return prefixed !== name && registered.includes(prefixed) ? void 0 : prefixed;
255
320
  }
321
+ function isExcluded(name, exclude) {
322
+ return exclude.some((rule) => {
323
+ if (typeof rule === "string") return rule === name;
324
+ rule.lastIndex = 0;
325
+ return rule.test(name);
326
+ });
327
+ }
328
+ function registeredAs(name, policy, registered) {
329
+ const as = autoImportedAs(name, policy.autoImports, registered);
330
+ return as === void 0 || isExcluded(as, policy.exclude) ? void 0 : as;
331
+ }
256
332
  const vue = (file) => `runtime/vue/${file}`;
257
333
  const composable = (file) => vue(`composables/${file}`);
258
334
  const asyncQuery = "runtime/nuxt/composables/use-async-query";
@@ -300,6 +376,7 @@ const APP_IMPORTS = {
300
376
  ],
301
377
  betterAuth: [
302
378
  { name: "useBetterAuth", from: "runtime/better-auth/vue/use-better-auth" },
379
+ { name: "loadAuthClient", from: "runtime/better-auth/vue/auth-client-loader" },
303
380
  { name: "usePreloadedAuthQuery", from: "runtime/better-auth/vue/hydration" },
304
381
  { name: "resolveAuthRedirect", from: "runtime/better-auth/vue/redirect" }
305
382
  ],
@@ -403,13 +480,16 @@ const module$1 = defineNuxtModule({
403
480
  for (const message of diagnostics.warnings) logger.warn(message);
404
481
  nuxt.options.runtimeConfig.public.convex.loginPath = diagnostics.loginPath;
405
482
  nuxt.options.runtimeConfig.public.convex.authRoute = diagnostics.authRoute;
483
+ nuxt.options.runtimeConfig.public.convex.deferAuthClient = nuxt.options.builder === "@nuxt/vite-builder";
406
484
  registerAuthClientAlias(resolver, nuxt, options);
485
+ keepAuthClientOffPrefetch(nuxt);
486
+ registerAuthClientOnEvaluation(resolver, nuxt);
407
487
  registerConvexAliases(nuxt);
408
488
  registerConvexTypeFallback(nuxt);
409
- const autoImports = options.autoImports ?? true;
410
- registerVueComposables(resolver, autoImports);
411
- registerAuthComponents(resolver, autoImports);
412
- const integrations = registerIntegrations(resolver, nuxt, options, autoImports, diagnostics.authRoute);
489
+ const policy = { autoImports: options.autoImports ?? true, exclude: options.exclude ?? [] };
490
+ registerVueComposables(resolver, policy);
491
+ registerAuthComponents(resolver, policy);
492
+ const integrations = registerIntegrations(resolver, nuxt, options, policy, diagnostics.authRoute);
413
493
  watchConvexCodegen(nuxt);
414
494
  if (nuxt.options.dev && options.devtools !== false && isDevtoolsUiEnabled(nuxt)) {
415
495
  const { setupDevtools } = await import('./chunks/index.mjs');
@@ -444,7 +524,7 @@ function isDevtoolsUiEnabled(nuxt) {
444
524
  const devtools = nuxt.options.devtools;
445
525
  return typeof devtools === "boolean" ? devtools : devtools?.enabled !== false;
446
526
  }
447
- function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
527
+ function registerIntegrations(resolver, nuxt, options, policy, authRoute) {
448
528
  const report = (key, pkg, state) => {
449
529
  if (state.missingPackage) {
450
530
  logger.error(`\`convex.${key}\` is enabled but \`${pkg}\` is not installed. Run \`npm install ${pkg}\` or remove the option.`);
@@ -455,21 +535,21 @@ function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
455
535
  const resolve = (key, pkg) => report(key, pkg, resolveIntegrationState(options[key], isPackageInstalled(pkg, rootDir), isDeclaredDependency(pkg, rootDir)));
456
536
  const betterAuth = resolve("betterAuth", "@convex-dev/better-auth");
457
537
  if (betterAuth) {
458
- registerBetterAuth(resolver, authRoute, autoImports);
538
+ registerBetterAuth(resolver, authRoute, policy);
459
539
  } else {
460
540
  registerBaseConvexClient(resolver);
461
541
  }
462
542
  const clerk = resolve("clerk", "@clerk/vue");
463
543
  if (clerk) {
464
- registerClerk(resolver, autoImports);
544
+ registerClerk(resolver, policy);
465
545
  }
466
546
  const auth0 = resolve("auth0", "@auth0/auth0-vue");
467
547
  if (auth0) {
468
- registerAuth0(resolver, autoImports);
548
+ registerAuth0(resolver, policy);
469
549
  }
470
550
  const polar = resolve("polar", "@convex-dev/polar");
471
551
  if (polar) {
472
- registerPolarComponents(resolver, autoImports);
552
+ registerPolarComponents(resolver, policy);
473
553
  }
474
554
  const security = report("security", "nuxt-security", resolveSecurityState(nuxt, options.security));
475
555
  if (security) {
@@ -577,15 +657,15 @@ function registerConvexTypeFallback(nuxt) {
577
657
  dependsOn: (change) => change.path.replace(/\\/g, "/").includes("_generated/api")
578
658
  }, { nuxt: true, nitro: true });
579
659
  }
580
- function registerVueComposables(resolver, autoImports) {
581
- registerIntegrationImports(resolver, "core", autoImports);
660
+ function registerVueComposables(resolver, policy) {
661
+ registerIntegrationImports(resolver, "core", policy);
582
662
  }
583
- function registerAuthComponents(resolver, autoImports) {
584
- registerComponents(resolver, "core", autoImports);
663
+ function registerAuthComponents(resolver, policy) {
664
+ registerComponents(resolver, "core", policy);
585
665
  }
586
- function registerIntegrationImports(resolver, integration, autoImports) {
666
+ function registerIntegrationImports(resolver, integration, policy) {
587
667
  const kept = (entries) => entries.flatMap((entry) => {
588
- const as = autoImportedAs(entry.name, autoImports, entries.map((e) => e.name));
668
+ const as = registeredAs(entry.name, policy, entries.map((e) => e.name));
589
669
  return as ? [{ ...entry, as, from: resolver.resolve(`./${entry.from}`) }] : [];
590
670
  });
591
671
  const app = kept(APP_IMPORTS[integration]);
@@ -593,10 +673,10 @@ function registerIntegrationImports(resolver, integration, autoImports) {
593
673
  if (app.length > 0) addImports(app);
594
674
  if (server.length > 0) addServerImports(server);
595
675
  }
596
- function registerComponents(resolver, integration, autoImports) {
676
+ function registerComponents(resolver, integration, policy) {
597
677
  const entries = APP_COMPONENTS[integration];
598
678
  for (const { name, from } of entries) {
599
- const as = autoImportedAs(name, autoImports, entries.map((e) => e.name));
679
+ const as = registeredAs(name, policy, entries.map((e) => e.name));
600
680
  if (as) addComponent({ name: as, filePath: resolver.resolve(`./${from}`), export: name });
601
681
  }
602
682
  }
@@ -604,11 +684,13 @@ const AUTH_PROXY_SECURITY_RULES = {
604
684
  xssValidator: false,
605
685
  allowedMethodsRestricter: { methods: ["GET", "HEAD", "POST", "OPTIONS"] }
606
686
  };
607
- function registerBetterAuth(resolver, authRoute, autoImports) {
687
+ function registerBetterAuth(resolver, authRoute, policy) {
608
688
  addPlugin(resolver.resolve("./runtime/better-auth/vue/plugin.client"));
609
689
  addPlugin(resolver.resolve("./runtime/better-auth/vue/plugin.server"));
610
- registerIntegrationImports(resolver, "betterAuth", autoImports);
611
- registerComponents(resolver, "betterAuth", autoImports);
690
+ const noSessionVary = resolver.resolve("./runtime/better-auth/nuxt/no-session-vary");
691
+ addNitroPlugin({ nitro2: noSessionVary, nitro3: noSessionVary });
692
+ registerIntegrationImports(resolver, "betterAuth", policy);
693
+ registerComponents(resolver, "betterAuth", policy);
612
694
  addServerHandler({
613
695
  route: `${authRoute}/**`,
614
696
  handler: { nuxt: resolver.resolve("./runtime/better-auth/nuxt/proxy") }
@@ -618,25 +700,27 @@ function registerBetterAuth(resolver, authRoute, autoImports) {
618
700
  prerender: false,
619
701
  security: AUTH_PROXY_SECURITY_RULES
620
702
  });
621
- addRouteMiddleware({
622
- name: "convex-auth",
623
- path: resolver.resolve("./runtime/better-auth/nuxt/middleware"),
624
- global: false
625
- });
703
+ if (!isExcluded("convex-auth", policy.exclude)) {
704
+ addRouteMiddleware({
705
+ name: "convex-auth",
706
+ path: resolver.resolve("./runtime/better-auth/nuxt/middleware"),
707
+ global: false
708
+ });
709
+ }
626
710
  }
627
711
  function registerBaseConvexClient(resolver) {
628
712
  addPlugin(resolver.resolve("./runtime/vue/plugin"));
629
713
  }
630
- function registerClerk(resolver, autoImports) {
631
- registerIntegrationImports(resolver, "clerk", autoImports);
632
- registerComponents(resolver, "clerk", autoImports);
714
+ function registerClerk(resolver, policy) {
715
+ registerIntegrationImports(resolver, "clerk", policy);
716
+ registerComponents(resolver, "clerk", policy);
633
717
  }
634
- function registerAuth0(resolver, autoImports) {
635
- registerIntegrationImports(resolver, "auth0", autoImports);
636
- registerComponents(resolver, "auth0", autoImports);
718
+ function registerAuth0(resolver, policy) {
719
+ registerIntegrationImports(resolver, "auth0", policy);
720
+ registerComponents(resolver, "auth0", policy);
637
721
  }
638
- function registerPolarComponents(resolver, autoImports) {
639
- registerComponents(resolver, "polar", autoImports);
722
+ function registerPolarComponents(resolver, policy) {
723
+ registerComponents(resolver, "polar", policy);
640
724
  }
641
725
 
642
726
  export { module$1 as default };
@@ -1,6 +1,7 @@
1
1
  import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
2
  import { watch } from "vue";
3
- import { useBetterAuth } from "../vue/use-better-auth.js";
3
+ import { clientSessionSettled, useBetterAuth } from "../vue/use-better-auth.js";
4
+ import { loadedAuthClient } from "../vue/auth-client-loader.js";
4
5
  import { loginTarget, serverGuard } from "./server-guard.js";
5
6
  function waitForSession(isPending) {
6
7
  return new Promise((resolve) => {
@@ -21,7 +22,12 @@ export default defineNuxtRouteMiddleware(async (to) => {
21
22
  if (import.meta.server) {
22
23
  return serverGuard(to, loginPath);
23
24
  }
24
- const { session } = useBetterAuth();
25
+ const { session, isAuthenticated } = useBetterAuth();
26
+ await clientSessionSettled();
27
+ if (!loadedAuthClient()) {
28
+ if (!isAuthenticated.value && to.path !== loginPath) return navigateTo(loginTarget(to, loginPath));
29
+ return;
30
+ }
25
31
  if (session.value.isPending) {
26
32
  await waitForSession(() => session.value.isPending);
27
33
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The slice of a response event this plugin reads and writes: Nitro 2's node
3
+ * response, or Nitro 3's web headers. Typed locally, so the published types
4
+ * don't depend on how the app resolves `nitropack`.
5
+ */
6
+ export interface NoSessionResponseEvent {
7
+ context: Record<string, unknown>;
8
+ node?: {
9
+ res?: {
10
+ getHeader?: (name: string) => unknown;
11
+ setHeader?: (name: string, value: string) => unknown;
12
+ };
13
+ };
14
+ res?: {
15
+ headers?: {
16
+ get: (name: string) => string | null;
17
+ set: (name: string, value: string) => void;
18
+ };
19
+ };
20
+ }
21
+ /** The Nitro app hook this plugin listens on. */
22
+ export interface NoSessionNitroApp {
23
+ hooks: {
24
+ hook: (name: 'beforeResponse', fn: (event: NoSessionResponseEvent) => void) => unknown;
25
+ };
26
+ }
27
+ /** `vary` with `Cookie` in it, keeping what it named already. */
28
+ export declare function withCookieVary(vary: unknown): string;
29
+ /**
30
+ * Nitro plugin: a response whose payload says the server saw no session, made
31
+ * cacheable after the page rendered (a `render:response` hook, a header set
32
+ * once the payload was written), varies on `Cookie`. The payload can no
33
+ * longer change by then; varying keeps a shared cache from handing that
34
+ * cookie-less render to a visitor whose request carries a session cookie.
35
+ * Runs on `beforeResponse`, after every `render:response` hook and the
36
+ * headers they set.
37
+ */
38
+ export default function noSessionVaryPlugin(nitroApp: NoSessionNitroApp): void;
@@ -0,0 +1,21 @@
1
+ import { CACHING_HEADERS, isSharedRender, NO_SESSION_CONTEXT_KEY } from "./shared-render.js";
2
+ function readHeader(event, name) {
3
+ return event.node?.res?.getHeader?.(name) ?? event.res?.headers?.get(name) ?? void 0;
4
+ }
5
+ function writeHeader(event, name, value) {
6
+ if (event.node?.res?.setHeader) event.node.res.setHeader(name, value);
7
+ else event.res?.headers?.set(name, value);
8
+ }
9
+ export function withCookieVary(vary) {
10
+ const names = (Array.isArray(vary) ? vary.join(",") : typeof vary === "string" ? vary : "").split(",").map((name) => name.trim()).filter(Boolean);
11
+ if (names.includes("*") || names.some((name) => name.toLowerCase() === "cookie")) return names.join(", ");
12
+ return [...names, "Cookie"].join(", ");
13
+ }
14
+ export default function noSessionVaryPlugin(nitroApp) {
15
+ nitroApp.hooks.hook("beforeResponse", (event) => {
16
+ if (event.context[NO_SESSION_CONTEXT_KEY] !== true) return;
17
+ const headers = Object.fromEntries(CACHING_HEADERS.map((name) => [name, readHeader(event, name)]));
18
+ if (!isSharedRender({ headers })) return;
19
+ writeHeader(event, "vary", withCookieVary(readHeader(event, "vary")));
20
+ });
21
+ }
@@ -1,6 +1,7 @@
1
1
  import { getToken } from "@convex-dev/better-auth/utils";
2
2
  import { getConvexRuntimeConfig } from "../../nuxt/config.js";
3
3
  import { fetchAction, fetchMutation, fetchQuery, preloadQuery } from "../../nuxt/index.js";
4
+ import { withoutFunctionName } from "../../nuxt/function-name.js";
4
5
  const parseConvexSiteUrl = (url) => {
5
6
  if (!url) {
6
7
  throw new Error(
@@ -107,7 +108,7 @@ export function convexAuth(event, opts) {
107
108
  try {
108
109
  return await fn(token?.token);
109
110
  } catch (error) {
110
- if (!opts?.jwtCache?.enabled || token.isFresh || !opts.jwtCache.isAuthError(error)) {
111
+ if (!opts?.jwtCache?.enabled || token.isFresh || !withoutFunctionName(error, opts.jwtCache.isAuthError.bind(opts.jwtCache))) {
111
112
  throw error;
112
113
  }
113
114
  const newToken = await cachedGetToken({ forceRefresh: true });
@@ -0,0 +1,9 @@
1
+ /** The parts of a request event `isSharedRender` reads. */
2
+ export interface RenderContext {
3
+ /** Nitro's matched route rules (`event.context._nitro.routeRules`), `swr`/`isr` normalised into `cache`. */
4
+ routeRules?: Record<string, unknown>;
5
+ /** Set by Nitro's cached handler on the render it caches (`event.context.cache`). */
6
+ cache?: unknown;
7
+ /** The response's caching headers as the render sees them, by lower-case name. */
8
+ headers?: Record<string, unknown>;
9
+ }
@@ -0,0 +1,17 @@
1
+ export const NO_SESSION_CONTEXT_KEY = "convexNoSession";
2
+ export const CACHING_HEADERS = ["cache-control", "cdn-cache-control", "surrogate-control"];
3
+ function headerText(value) {
4
+ if (Array.isArray(value)) return value.join(",");
5
+ return typeof value === "string" || typeof value === "number" ? String(value) : "";
6
+ }
7
+ function storesForOthers(name, value) {
8
+ const directives = value.toLowerCase().split(",").map((directive) => directive.trim().split("=")[0]).filter(Boolean);
9
+ if (directives.length === 0 || directives.includes("private") || directives.includes("no-store")) return false;
10
+ if (name !== "cache-control") return true;
11
+ return directives.includes("public") || directives.includes("s-maxage") || directives.includes("max-age");
12
+ }
13
+ export function isSharedRender({ routeRules = {}, cache, headers = {} }) {
14
+ if (cache || routeRules.cache || routeRules.swr || routeRules.isr || routeRules.prerender || routeRules.payload) return true;
15
+ const ruleHeaders = Object.fromEntries(Object.entries(routeRules.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
16
+ return CACHING_HEADERS.some((name) => storesForOthers(name, headerText(headers[name])) || storesForOthers(name, headerText(ruleHeaders[name])));
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { AuthClient } from '#convex/auth-client';
2
+ /**
3
+ * The app's Better Auth client (`#convex/auth-client`), loading it on the
4
+ * first call. Every call resolves to the same instance, the one a static
5
+ * `import { authClient } from 'nuxt-convex-module/better-auth/client'` holds.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * async function signOut() {
10
+ * const client = await loadAuthClient()
11
+ * await client.signOut()
12
+ * }
13
+ * ```
14
+ *
15
+ * @public
16
+ */
17
+ export declare function loadAuthClient(): Promise<AuthClient>;
@@ -0,0 +1,186 @@
1
+ import { getCurrentScope, onScopeDispose, shallowRef, watch } from "vue";
2
+ let loaded;
3
+ let loading;
4
+ let deferred = false;
5
+ const waiting = /* @__PURE__ */ new Set();
6
+ let evaluated;
7
+ let stopSignal;
8
+ export function loadAuthClient() {
9
+ if (evaluated) registerAuthClient(evaluated);
10
+ loading ??= import("#convex/auth-client").then(
11
+ ({ authClient }) => {
12
+ registerAuthClient(authClient);
13
+ return authClient;
14
+ },
15
+ (error) => {
16
+ loading = void 0;
17
+ throw error;
18
+ }
19
+ );
20
+ return loading;
21
+ }
22
+ export function registerEvaluatedAuthClient(client) {
23
+ if (loaded || evaluated === client) return;
24
+ if (!deferred) return registerAuthClient(client);
25
+ evaluated = client;
26
+ const signal = client.$store?.atoms?.$sessionSignal;
27
+ stopSignal = signal?.listen?.(() => registerAuthClient(client));
28
+ }
29
+ export function registerAuthClient(client) {
30
+ if (loaded) return;
31
+ stopSignal?.();
32
+ stopSignal = void 0;
33
+ evaluated = void 0;
34
+ loaded = client;
35
+ loading ??= Promise.resolve(client);
36
+ const listeners = [...waiting];
37
+ waiting.clear();
38
+ for (const listener of listeners) {
39
+ try {
40
+ listener(client);
41
+ } catch (error) {
42
+ report(error);
43
+ }
44
+ }
45
+ }
46
+ function report(error) {
47
+ if (typeof globalThis.reportError === "function") globalThis.reportError(error);
48
+ else queueMicrotask(() => {
49
+ throw error;
50
+ });
51
+ }
52
+ export function loadedAuthClient() {
53
+ return loaded;
54
+ }
55
+ export function onAuthClient(listener) {
56
+ if (loaded) {
57
+ listener(loaded);
58
+ return () => {
59
+ };
60
+ }
61
+ waiting.add(listener);
62
+ return () => waiting.delete(listener);
63
+ }
64
+ export function deferAuthClient() {
65
+ if (deferred || loaded) return;
66
+ deferred = true;
67
+ const onVisible = () => {
68
+ if (document.visibilityState !== "visible") return;
69
+ void loadAuthClient().then(() => document.removeEventListener("visibilitychange", onVisible), () => {
70
+ });
71
+ };
72
+ document.addEventListener("visibilitychange", onVisible);
73
+ }
74
+ export function retryAuthClientLoad() {
75
+ if (loaded || typeof window === "undefined") return;
76
+ const retry = () => {
77
+ if (document.visibilityState !== "visible") return;
78
+ void loadAuthClient().then(() => {
79
+ window.removeEventListener("online", retry);
80
+ document.removeEventListener("visibilitychange", retry);
81
+ }, () => {
82
+ });
83
+ };
84
+ window.addEventListener("online", retry);
85
+ document.addEventListener("visibilitychange", retry);
86
+ }
87
+ export function isAuthClientDeferred() {
88
+ return deferred && !loaded;
89
+ }
90
+ function reach(client, path) {
91
+ let owner;
92
+ let value = client;
93
+ for (const key of path) {
94
+ owner = value;
95
+ value = value[key];
96
+ }
97
+ return { owner, value };
98
+ }
99
+ function call(client, path, args) {
100
+ const { owner, value } = reach(client, path);
101
+ return Reflect.apply(value, owner, args);
102
+ }
103
+ const HOOK = /^use[A-Z]/;
104
+ function pendingSubscription(path, args) {
105
+ let unsubscribe;
106
+ const stopWaiting = onAuthClient((client) => {
107
+ unsubscribe = call(client, path, args);
108
+ });
109
+ void loadAuthClient().catch(() => {
110
+ });
111
+ return () => {
112
+ stopWaiting();
113
+ unsubscribe?.();
114
+ };
115
+ }
116
+ function pendingHook(path, args) {
117
+ const scope = getCurrentScope();
118
+ const refetch = async (...refetchArgs) => {
119
+ await loadAuthClient();
120
+ const next = state.value.refetch;
121
+ if (next === refetch) return;
122
+ return next(...refetchArgs);
123
+ };
124
+ const state = shallowRef({
125
+ data: null,
126
+ error: null,
127
+ isPending: true,
128
+ isRefetching: false,
129
+ refetch
130
+ });
131
+ if (import.meta.server) return state;
132
+ const stopWaiting = onAuthClient((client) => {
133
+ const follow = () => watch(call(client, path, args), (value) => {
134
+ state.value = value;
135
+ }, { immediate: true, flush: "sync" });
136
+ if (!scope) follow();
137
+ else if (scope.active) scope.run(follow);
138
+ });
139
+ if (scope) onScopeDispose(stopWaiting);
140
+ void loadAuthClient().catch(() => loadAuthClient()).catch((error) => {
141
+ if (!loaded) state.value = { ...state.value, isPending: false, error };
142
+ });
143
+ return state;
144
+ }
145
+ const withReceivers = /* @__PURE__ */ new WeakMap();
146
+ function withReceiver(member, owner) {
147
+ if (typeof member !== "function" || typeof owner !== "object" || owner === null) return member;
148
+ let byMethod = withReceivers.get(owner);
149
+ if (!byMethod) withReceivers.set(owner, byMethod = /* @__PURE__ */ new WeakMap());
150
+ let wrapped = byMethod.get(member);
151
+ if (!wrapped) {
152
+ wrapped = new Proxy(member, {
153
+ apply: (fn, _this, args) => Reflect.apply(fn, owner, args)
154
+ });
155
+ byMethod.set(member, wrapped);
156
+ }
157
+ return wrapped;
158
+ }
159
+ function lazyPath(path) {
160
+ return new Proxy(() => {
161
+ }, {
162
+ get(_target, key) {
163
+ if (loaded) {
164
+ const owner = reach(loaded, path).value;
165
+ return withReceiver(owner[key], owner);
166
+ }
167
+ if (key === "then") return void 0;
168
+ return lazyPath([...path, key]);
169
+ },
170
+ apply(_target, _this, args) {
171
+ if (loaded) return call(loaded, path, args);
172
+ const name = path.at(-1);
173
+ if (typeof name === "string" && HOOK.test(name) && typeof args[0] !== "function") return pendingHook(path, args);
174
+ if (name === "subscribe" || name === "listen") return pendingSubscription(path, args);
175
+ return loadAuthClient().then((client) => call(client, path, args));
176
+ }
177
+ });
178
+ }
179
+ export const lazyAuthClient = lazyPath([]);
180
+ export function __resetAuthClientLoaderForTests() {
181
+ deferred = false;
182
+ stopSignal?.();
183
+ stopSignal = void 0;
184
+ evaluated = void 0;
185
+ waiting.clear();
186
+ }
@@ -1,4 +1,4 @@
1
- import { authClient } from '#convex/auth-client';
1
+ import type { AuthClient } from '#convex/auth-client';
2
2
  /**
3
3
  * An {@link AuthClient} that also includes the `crossDomainClient()` plugin.
4
4
  *
@@ -8,7 +8,7 @@ import { authClient } from '#convex/auth-client';
8
8
  *
9
9
  * @public
10
10
  */
11
- export type AuthClientWithCrossDomain = typeof authClient & {
11
+ export type AuthClientWithCrossDomain = AuthClient & {
12
12
  updateSession: () => void;
13
13
  crossDomain?: {
14
14
  oneTimeToken: {