use-convex 1.1.0 → 1.3.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 CHANGED
@@ -88,13 +88,20 @@ prewarmQuery(api.tasks.list, {})
88
88
  Browser-only live map (no SSR). Each value is `data | undefined` (loading) | `Error`. Combine with `useConvexQuery` when you need an SSR snapshot for known queries.
89
89
 
90
90
  ```ts
91
- const results = useConvexQueries(() => ({
92
- tasks: { query: api.tasks.list, args: {} },
93
- files: showFiles.value ? { query: api.files.list, args: {} } : 'skip',
94
- }))
91
+ const results = useConvexQueries(
92
+ () => ({
93
+ tasks: { query: api.tasks.list, args: {} },
94
+ files: showFiles.value ? { query: api.files.list, args: {} } : 'skip',
95
+ // Per-entry gate (overrides the options bag when set):
96
+ // admin: { query: api.admin.stats, args: {}, authenticated: true },
97
+ }),
98
+ { authenticated: true }, // wait for Convex auth before any subscribe
99
+ )
95
100
  // results.value.tasks
96
101
  ```
97
102
 
103
+ `'skip'` still wins for missing ids / feature flags. Prefer `{ authenticated: true }` over wrapping every private entry yourself.
104
+
98
105
  ### Pagination
99
106
 
100
107
  ```ts
@@ -193,10 +200,34 @@ convex: {
193
200
  auth: {
194
201
  provider: 'convex-auth',
195
202
  // cookie defaults to 'convex_jwt'
203
+ // storageNamespace: 'myapp', // optional; default = deployment URL
204
+ // storage: 'localStorage', // or 'inMemory' (verifier uses a short-lived cookie)
205
+ // shouldHandleCode: true, // set false to ignore ?code=
196
206
  },
197
207
  }
198
208
  ```
199
209
 
210
+ For non-default OAuth / callback flows (JS router `replaceURL`, function `shouldHandleCode`, custom `TokenStorage`), call `configureConvexAuth` from a client plugin that runs before the auth plugin:
211
+
212
+ ```ts
213
+ // plugins/convex-auth-options.client.ts
214
+ export default defineNuxtPlugin({
215
+ name: 'convex-auth-options',
216
+ enforce: 'pre',
217
+ setup() {
218
+ const route = useRoute()
219
+ configureConvexAuth({
220
+ replaceURL: (url) => navigateTo(url, { replace: true }),
221
+ shouldHandleCode: () => route.path === '/auth/callback',
222
+ // storage: window.sessionStorage, // may be async; signIn waits before redirect
223
+ // storageNamespace: 'myapp',
224
+ })
225
+ },
226
+ })
227
+ ```
228
+
229
+ `storage: 'inMemory'` keeps JWTs in memory. The OAuth verifier is a short-lived cookie so the `?code=` callback still works after the IdP redirect. Custom storage may return Promises; `signIn` waits for writes before navigating.
230
+
200
231
  ```vue
201
232
  <script setup lang="ts">
202
233
  const { signIn, signOut } = useAuth()
@@ -318,7 +349,7 @@ export default defineEventHandler(async (event) => {
318
349
  })
319
350
  ```
320
351
 
321
- `fetchAction` uses the same options. Each helper builds a fresh `ConvexHttpClient`. Override with `{ token }` when you already have a JWT. `getConvexToken(event)` reads the cookie without throwing.
352
+ `fetchAction` uses the same options. Each helper builds a fresh `ConvexHttpClient`. Override with `{ token }` when you already have a JWT. Pass `{ adminToken }` (deploy key / admin key) for privileged server tooling — same as Next.js `fetchQuery` (user `token` is ignored when `adminToken` is set). `getConvexToken(event)` reads the cookie without throwing.
322
353
 
323
354
  ## Config
324
355
 
@@ -332,6 +363,9 @@ convex: {
332
363
  cookie: 'convex_jwt',
333
364
  httpOnly: false,
334
365
  presentCookie: 'convex_auth_present',
366
+ storage: 'localStorage', // or 'inMemory'
367
+ // storageNamespace: 'myapp',
368
+ // shouldHandleCode: true,
335
369
  },
336
370
  }
337
371
  ```
package/dist/module.d.mts CHANGED
@@ -7,11 +7,13 @@ export { OptimisticUpdate, UseConvexMutationOptions } from '../dist/runtime/comp
7
7
  export { ConvexFileUploadExtraArgs, ConvexFileUploadMeta, UseConvexFileUploadOptions } from '../dist/runtime/composables/useConvexFileUpload.js';
8
8
  export { ConvexR2UploadApi, ConvexR2UploadProgress } from '../dist/runtime/composables/useConvexR2Upload.js';
9
9
  export { PaginatedQueryArgs, PaginatedQueryItem, PaginatedQueryReference, PaginationStatus, UseConvexPaginatedQueryOptions, UseConvexPaginatedQueryReturn } from '../dist/runtime/composables/useConvexPaginatedQuery.js';
10
- export { ConvexQueriesRequest, ConvexQueriesResult, ConvexQueryRequestEntry } from '../dist/runtime/composables/useConvexQueries.js';
10
+ export { ConvexQueriesRequest, ConvexQueriesResult, ConvexQueryRequestEntry, UseConvexQueriesOptions } from '../dist/runtime/composables/useConvexQueries.js';
11
11
  export { ConvexFetchOptions } from '../dist/runtime/server/fetch.js';
12
+ export { ConvexRef } from '../dist/runtime/utils/functionReference.js';
12
13
  export { ConvexAuthContext, ConvexNuxtContext } from '../dist/runtime/utils/context.js';
13
14
  export { ConnectionState } from '../dist/runtime/composables/useConvexConnectionState.js';
14
15
  export { ConvexGate } from '../dist/runtime/composables/useConvexGate.js';
16
+ export { ConvexAuthClientOptions, ConvexAuthStorageMode, ConvexAuthTokenStorage, configureConvexAuth } from '../dist/runtime/utils/authClientOptions.js';
15
17
 
16
18
  interface ModuleAuthOptions {
17
19
  /**
@@ -58,6 +60,24 @@ interface ModuleAuthOptions {
58
60
  * @default 'convex_auth_present'
59
61
  */
60
62
  presentCookie?: string;
63
+ /**
64
+ * Namespace for local token keys (React `storageNamespace` parity).
65
+ * Non-alphanumeric characters are stripped. Defaults to the deployment URL.
66
+ */
67
+ storageNamespace?: string;
68
+ /**
69
+ * Client token storage. `'localStorage'` (default) or `'inMemory'`.
70
+ * For a custom TokenStorage or `replaceURL` / function `shouldHandleCode`,
71
+ * call `configureConvexAuth` from a client plugin with `enforce: 'pre'`.
72
+ * Ignored when `httpOnly` is enabled.
73
+ * @default 'localStorage'
74
+ */
75
+ storage?: 'localStorage' | 'inMemory';
76
+ /**
77
+ * When `false`, do not consume `?code=` OAuth callbacks.
78
+ * For a function gate, use `configureConvexAuth({ shouldHandleCode })`.
79
+ */
80
+ shouldHandleCode?: boolean;
61
81
  }
62
82
  interface ModuleOptions {
63
83
  /**
package/dist/module.mjs CHANGED
@@ -3,6 +3,7 @@ import { defu } from 'defu';
3
3
  import { resolveAuthCookieName } from '../dist/runtime/utils/authStorage.js';
4
4
  import { suggestedCloudUrlFromMismatch } from '../dist/runtime/utils/convexDashboard.js';
5
5
  import { warnStaleLocalConvexUrl } from '../dist/runtime/utils/errors.js';
6
+ export { configureConvexAuth } from '../dist/runtime/utils/authClientOptions.js';
6
7
 
7
8
  const module$1 = defineNuxtModule({
8
9
  meta: {
@@ -185,6 +186,10 @@ const module$1 = defineNuxtModule({
185
186
  {
186
187
  name: "signOut",
187
188
  from: resolver.resolve("./runtime/composables/useAuth")
189
+ },
190
+ {
191
+ name: "configureConvexAuth",
192
+ from: resolver.resolve("./runtime/utils/authClientOptions")
188
193
  }
189
194
  ] : []
190
195
  ]);
@@ -1,4 +1,5 @@
1
- import type { FunctionArgs, FunctionReference } from 'convex/server';
1
+ import type { FunctionArgs } from 'convex/server';
2
+ import type { ConvexRef } from '../utils/functionReference.js';
2
3
  /**
3
4
  * Start a live subscription early so the first `useConvexQuery` / screen
4
5
  * paint can reuse warm client state (React `prewarmQuery` parity).
@@ -6,4 +7,4 @@ import type { FunctionArgs, FunctionReference } from 'convex/server';
6
7
  * Returns an unsubscribe function. When called during Vue `setup`, the
7
8
  * subscription is also cleaned up with the current effect scope.
8
9
  */
9
- export declare function prewarmQuery<Query extends FunctionReference<'query'>>(query: Query, args?: FunctionArgs<Query>): () => void;
10
+ export declare function prewarmQuery<Query extends ConvexRef<'query'>>(query: Query, args?: FunctionArgs<Query>): () => void;
@@ -6,16 +6,21 @@ import { useConvexAuth } from "./useConvexAuth.js";
6
6
  import { missingConvexUrlError, unreachableConvexUrlError } from "../utils/errors.js";
7
7
  import { withRefreshMutex } from "../utils/authMutex.js";
8
8
  import { useAuthJwtCookie, useAuthPresentCookie } from "../utils/authCookie.js";
9
+ import { getConvexAuthClientOptions, setConvexAuthModuleDefaults } from "../utils/authClientOptions.js";
9
10
  import {
10
11
  flattenSignInParams,
11
12
  isHttpOnlyAuth,
12
13
  JWT_STORAGE_KEY,
13
- readLocal,
14
+ readLocalAsync,
15
+ readStored,
16
+ readVerifier,
17
+ readVerifierAsync,
14
18
  REFRESH_TOKEN_STORAGE_KEY,
15
19
  shouldConsumeOAuthCode,
16
20
  storageKey,
17
21
  VERIFIER_STORAGE_KEY,
18
- writeLocal
22
+ writeLocalAsync,
23
+ writeVerifier
19
24
  } from "../utils/authStorage.js";
20
25
  import { tryUseConvexContext } from "../utils/context.js";
21
26
  import { parseOAuthRedirect } from "../utils/oauthRedirect.js";
@@ -61,8 +66,8 @@ export async function signIn(provider, params) {
61
66
  session.error.value = null;
62
67
  try {
63
68
  const flatParams = flattenSignInParams(params);
64
- const existingVerifier = readLocal(session.verifierKey.value) ?? void 0;
65
- writeLocal(session.verifierKey.value, null);
69
+ const existingVerifier = await readVerifierAsync(session.verifierKey.value) ?? void 0;
70
+ await writeVerifier(session.verifierKey.value, null);
66
71
  const result = await callSignIn(convexUrl, {
67
72
  provider,
68
73
  params: flatParams,
@@ -71,9 +76,9 @@ export async function signIn(provider, params) {
71
76
  if (result.redirect !== void 0) {
72
77
  const url = parseOAuthRedirect(result.redirect);
73
78
  if (result.verifier) {
74
- writeLocal(session.verifierKey.value, result.verifier);
79
+ await writeVerifier(session.verifierKey.value, result.verifier);
75
80
  }
76
- if (import.meta.client) {
81
+ if (inBrowser()) {
77
82
  window.location.href = url.toString();
78
83
  }
79
84
  return { signingIn: false, redirect: url };
@@ -98,7 +103,7 @@ export async function signOut() {
98
103
  session.error.value = null;
99
104
  try {
100
105
  if (convexUrl) {
101
- const token = readLocal(session.jwtKey.value);
106
+ const token = await readLocalAsync(session.jwtKey.value);
102
107
  const http = new ConvexHttpClient(convexUrl);
103
108
  if (token) {
104
109
  http.setAuth(token);
@@ -140,12 +145,23 @@ export function hydrateAuthFromStorage() {
140
145
  });
141
146
  return;
142
147
  }
143
- const token = readLocal(session.jwtKey.value);
144
- session.hasSession.value = token !== null;
145
- session.isLoading.value = false;
146
- if (token) {
147
- setJwtCookie(token);
148
+ const stored = readStored(session.jwtKey.value);
149
+ const applyToken = (token) => {
150
+ session.hasSession.value = token !== null;
151
+ session.isLoading.value = false;
152
+ if (token) {
153
+ setJwtCookie(token);
154
+ }
155
+ };
156
+ if (stored instanceof Promise) {
157
+ session.isLoading.value = true;
158
+ void stored.then(applyToken).catch(() => {
159
+ session.hasSession.value = false;
160
+ session.isLoading.value = false;
161
+ });
162
+ return;
148
163
  }
164
+ applyToken(stored);
149
165
  }
150
166
  export async function getAuthToken({
151
167
  forceRefreshToken
@@ -175,31 +191,37 @@ export async function getAuthToken({
175
191
  });
176
192
  }
177
193
  if (!forceRefreshToken) {
178
- return readLocal(session.jwtKey.value);
194
+ return await readLocalAsync(session.jwtKey.value);
179
195
  }
180
196
  return withRefreshMutex(session.refreshKey.value, async () => {
181
- const refreshToken = readLocal(session.refreshKey.value);
197
+ const refreshToken = await readLocalAsync(session.refreshKey.value);
182
198
  if (!refreshToken) {
183
- persistTokens(session, null);
199
+ await persistTokens(session, null);
184
200
  return null;
185
201
  }
186
202
  try {
187
203
  const result = await callSignInWithRetry(convexUrl, { refreshToken });
188
204
  const tokens = result.tokens ?? null;
189
205
  if (!tokens) {
190
- persistTokens(session, null);
206
+ await persistTokens(session, null);
191
207
  return null;
192
208
  }
193
- persistTokens(session, tokens);
209
+ await persistTokens(session, tokens);
194
210
  return tokens.token;
195
211
  } catch {
196
- persistTokens(session, null);
212
+ await persistTokens(session, null);
197
213
  return null;
198
214
  }
199
215
  });
200
216
  }
217
+ function inBrowser() {
218
+ if (import.meta.client) {
219
+ return true;
220
+ }
221
+ return import.meta.client !== false && typeof window !== "undefined";
222
+ }
201
223
  export function hasPendingOAuthCallback() {
202
- if (!import.meta.client) {
224
+ if (!inBrowser()) {
203
225
  return false;
204
226
  }
205
227
  const session = useAuthSession();
@@ -207,11 +229,25 @@ export function hasPendingOAuthCallback() {
207
229
  return false;
208
230
  }
209
231
  const code = new URLSearchParams(window.location.search).get("code");
210
- const verifier = readLocal(session.verifierKey.value);
211
- return shouldConsumeOAuthCode({ code, verifier });
232
+ if (!code) {
233
+ return false;
234
+ }
235
+ const gate = getConvexAuthClientOptions().shouldHandleCode;
236
+ if (gate === false || typeof gate === "function" && !gate()) {
237
+ return false;
238
+ }
239
+ const verifier = readVerifier(session.verifierKey.value);
240
+ if (verifier instanceof Promise) {
241
+ return true;
242
+ }
243
+ return shouldConsumeOAuthCode({
244
+ code,
245
+ verifier,
246
+ shouldHandleCode: gate
247
+ });
212
248
  }
213
249
  export async function consumeOAuthCodeFromUrl() {
214
- if (!import.meta.client) {
250
+ if (!inBrowser()) {
215
251
  return false;
216
252
  }
217
253
  const session = useAuthSession();
@@ -219,18 +255,36 @@ export async function consumeOAuthCodeFromUrl() {
219
255
  return false;
220
256
  }
221
257
  const code = new URLSearchParams(window.location.search).get("code");
222
- const verifier = readLocal(session.verifierKey.value);
223
- if (!shouldConsumeOAuthCode({ code, verifier })) {
258
+ const verifier = await readVerifierAsync(session.verifierKey.value);
259
+ const gate = getConvexAuthClientOptions().shouldHandleCode;
260
+ if (!shouldConsumeOAuthCode({
261
+ code,
262
+ verifier,
263
+ shouldHandleCode: gate
264
+ })) {
224
265
  return false;
225
266
  }
226
267
  const url = new URL(window.location.href);
227
268
  url.searchParams.delete("code");
228
- window.history.replaceState({}, "", url.pathname + url.search + url.hash);
269
+ const relativeUrl = url.pathname + url.search + url.hash;
270
+ const replaceURL = getConvexAuthClientOptions().replaceURL;
271
+ try {
272
+ if (replaceURL) {
273
+ await replaceURL(relativeUrl);
274
+ } else {
275
+ window.history.replaceState({}, "", relativeUrl);
276
+ }
277
+ } catch (cause) {
278
+ session.error.value = cause instanceof Error ? cause.message : "OAuth callback failed";
279
+ session.isLoading.value = false;
280
+ session.pending.value = false;
281
+ return false;
282
+ }
229
283
  session.pending.value = true;
230
284
  session.error.value = null;
231
285
  session.isLoading.value = true;
232
286
  try {
233
- writeLocal(session.verifierKey.value, null);
287
+ await writeVerifier(session.verifierKey.value, null);
234
288
  const result = await callSignInWithRetry(session.convexUrl, {
235
289
  params: { code },
236
290
  verifier
@@ -257,19 +311,28 @@ function useAuthSession() {
257
311
  const config = useRuntimeConfig();
258
312
  const convexConfig = config.public.convex;
259
313
  const convexUrl = convexConfig?.url;
260
- const httpOnly = isHttpOnlyAuth(convexConfig?.auth);
314
+ const authConfig = convexConfig?.auth;
315
+ const httpOnly = isHttpOnlyAuth(authConfig);
316
+ if (authConfig?.provider === "convex-auth") {
317
+ setConvexAuthModuleDefaults({
318
+ storageNamespace: authConfig.storageNamespace,
319
+ storage: authConfig.storage,
320
+ shouldHandleCode: authConfig.shouldHandleCode
321
+ });
322
+ }
261
323
  const isLoading = useState("convex-auth-loading", () => true);
262
324
  const hasSession = useState("convex-auth-has-session", () => false);
263
325
  const error = useState("convex-auth-error", () => null);
264
326
  const pending = useState("convex-auth-pending", () => false);
327
+ const namespace = getConvexAuthClientOptions().storageNamespace ?? authConfig?.storageNamespace ?? convexUrl;
265
328
  const jwtKey = computed(
266
- () => convexUrl ? storageKey(JWT_STORAGE_KEY, convexUrl) : JWT_STORAGE_KEY
329
+ () => namespace ? storageKey(JWT_STORAGE_KEY, namespace) : JWT_STORAGE_KEY
267
330
  );
268
331
  const refreshKey = computed(
269
- () => convexUrl ? storageKey(REFRESH_TOKEN_STORAGE_KEY, convexUrl) : REFRESH_TOKEN_STORAGE_KEY
332
+ () => namespace ? storageKey(REFRESH_TOKEN_STORAGE_KEY, namespace) : REFRESH_TOKEN_STORAGE_KEY
270
333
  );
271
334
  const verifierKey = computed(
272
- () => convexUrl ? storageKey(VERIFIER_STORAGE_KEY, convexUrl) : VERIFIER_STORAGE_KEY
335
+ () => namespace ? storageKey(VERIFIER_STORAGE_KEY, namespace) : VERIFIER_STORAGE_KEY
273
336
  );
274
337
  return {
275
338
  convexUrl,
@@ -353,8 +416,8 @@ async function persistTokens(session, tokens) {
353
416
  if (session.httpOnly) {
354
417
  if (tokens === null) {
355
418
  await writeHttpOnlySession(null);
356
- writeLocal(session.jwtKey.value, null);
357
- writeLocal(session.refreshKey.value, null);
419
+ await writeLocalAsync(session.jwtKey.value, null);
420
+ await writeLocalAsync(session.refreshKey.value, null);
358
421
  session.hasSession.value = false;
359
422
  session.isLoading.value = false;
360
423
  return;
@@ -365,15 +428,15 @@ async function persistTokens(session, tokens) {
365
428
  return;
366
429
  }
367
430
  if (tokens === null) {
368
- writeLocal(session.jwtKey.value, null);
369
- writeLocal(session.refreshKey.value, null);
431
+ await writeLocalAsync(session.jwtKey.value, null);
432
+ await writeLocalAsync(session.refreshKey.value, null);
370
433
  setJwtCookie(null);
371
434
  session.hasSession.value = false;
372
435
  session.isLoading.value = false;
373
436
  return;
374
437
  }
375
- writeLocal(session.jwtKey.value, tokens.token);
376
- writeLocal(session.refreshKey.value, tokens.refreshToken);
438
+ await writeLocalAsync(session.jwtKey.value, tokens.token);
439
+ await writeLocalAsync(session.refreshKey.value, tokens.refreshToken);
377
440
  session.hasSession.value = true;
378
441
  session.isLoading.value = false;
379
442
  nextTick(() => {
@@ -1,12 +1,13 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
2
  import { type MaybeRefOrGetter } from 'vue';
3
+ import type { ConvexRef } from '../utils/functionReference.js';
3
4
  /**
4
5
  * Browser action helper. Returns `{ run, error, pending }`.
5
6
  *
6
7
  * Safe to call during SSR setup — the ConvexClient is only touched when
7
8
  * `run()` runs in the browser.
8
9
  */
9
- export declare function useConvexAction<Action extends FunctionReference<'action'>>(action: Action): {
10
+ export declare function useConvexAction<Action extends ConvexRef<'action'>>(action: Action): {
10
11
  run: (args?: MaybeRefOrGetter<FunctionArgs<Action>>) => Promise<FunctionReturnType<Action>>;
11
12
  error: import("vue").Ref<Error | null, Error | null>;
12
13
  pending: import("vue").ComputedRef<boolean>;
@@ -1,4 +1,5 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
+ import type { ConvexRef } from '../utils/functionReference.js';
2
3
  /** Built-in fields the composable always passes into `saveFile`. */
3
4
  export type ConvexFileUploadMeta = {
4
5
  storageId: string;
@@ -6,8 +7,8 @@ export type ConvexFileUploadMeta = {
6
7
  contentType: string;
7
8
  size: number;
8
9
  };
9
- export type ConvexFileUploadExtraArgs<SaveFile extends FunctionReference<'mutation'>> = Omit<FunctionArgs<SaveFile>, 'storageId' | 'name' | 'contentType' | 'size'>;
10
- export interface UseConvexFileUploadOptions<GenerateUploadUrl extends FunctionReference<'mutation'>, SaveFile extends FunctionReference<'mutation'>> {
10
+ export type ConvexFileUploadExtraArgs<SaveFile extends ConvexRef<'mutation'>> = Omit<FunctionArgs<SaveFile>, 'storageId' | 'name' | 'contentType' | 'size'>;
11
+ export interface UseConvexFileUploadOptions<GenerateUploadUrl extends ConvexRef<'mutation'>, SaveFile extends ConvexRef<'mutation'>> {
11
12
  /**
12
13
  * Mutation that returns a short-lived Convex upload URL
13
14
  * (`ctx.storage.generateUploadUrl()`). Must enforce auth itself.
@@ -35,7 +36,7 @@ export declare function postFileToUploadUrl(uploadUrl: string, file: File, onPro
35
36
  * in the browser. Your Convex mutations must enforce auth; never expose an
36
37
  * unauthenticated `generateUploadUrl`.
37
38
  */
38
- export declare function useConvexFileUpload<GenerateUploadUrl extends FunctionReference<'mutation'>, SaveFile extends FunctionReference<'mutation'>>(options: UseConvexFileUploadOptions<GenerateUploadUrl, SaveFile>): {
39
+ export declare function useConvexFileUpload<GenerateUploadUrl extends ConvexRef<'mutation'>, SaveFile extends ConvexRef<'mutation'>>(options: UseConvexFileUploadOptions<GenerateUploadUrl, SaveFile>): {
39
40
  upload: (file: File, extra?: ConvexFileUploadExtraArgs<SaveFile> extends Record<string, never> ? undefined : ConvexFileUploadExtraArgs<SaveFile>) => Promise<FunctionReturnType<SaveFile>>;
40
41
  error: import("vue").Ref<Error | null, Error | null>;
41
42
  pending: import("vue").ComputedRef<boolean>;
@@ -1,8 +1,9 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
2
  import type { OptimisticUpdate } from 'convex/browser';
3
3
  import { type MaybeRefOrGetter } from 'vue';
4
+ import type { ConvexRef } from '../utils/functionReference.js';
4
5
  export type { OptimisticUpdate };
5
- export interface UseConvexMutationOptions<Mutation extends FunctionReference<'mutation'>> {
6
+ export interface UseConvexMutationOptions<Mutation extends ConvexRef<'mutation'>> {
6
7
  /**
7
8
  * Local query update applied while the mutation is in flight.
8
9
  * Passed through to `ConvexClient.mutation` → `BaseConvexClient`.
@@ -18,7 +19,7 @@ export interface UseConvexMutationOptions<Mutation extends FunctionReference<'mu
18
19
  * Pass `{ optimisticUpdate }` to apply temporary local query results while
19
20
  * the mutation is in flight (same contract as React `withOptimisticUpdate`).
20
21
  */
21
- export declare function useConvexMutation<Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, options?: UseConvexMutationOptions<Mutation>): {
22
+ export declare function useConvexMutation<Mutation extends ConvexRef<'mutation'>>(mutation: Mutation, options?: UseConvexMutationOptions<Mutation>): {
22
23
  mutate: (args?: MaybeRefOrGetter<FunctionArgs<Mutation>>) => Promise<FunctionReturnType<Mutation>>;
23
24
  error: import("vue").Ref<Error | null, Error | null>;
24
25
  pending: import("vue").ComputedRef<boolean>;
@@ -1,4 +1,4 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType, PaginationOptions, PaginationResult } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReference, FunctionReference_future, FunctionReturnType, PaginationOptions, PaginationResult } from 'convex/server';
2
2
  import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
3
3
  /**
4
4
  * A query usable with {@link useConvexPaginatedQuery}.
@@ -6,6 +6,8 @@ import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
6
6
  */
7
7
  export type PaginatedQueryReference = FunctionReference<'query', 'public', {
8
8
  paginationOpts: PaginationOptions;
9
+ }, PaginationResult<unknown>> | FunctionReference_future<'query', 'public', {
10
+ paginationOpts: PaginationOptions;
9
11
  }, PaginationResult<unknown>>;
10
12
  export type PaginatedQueryArgs<Query extends PaginatedQueryReference> = Omit<FunctionArgs<Query>, 'paginationOpts'>;
11
13
  export type PaginatedQueryItem<Query extends PaginatedQueryReference> = FunctionReturnType<Query>['page'][number];
@@ -1,32 +1,62 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
2
  import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
+ import type { ConvexRef } from '../utils/functionReference.js';
3
4
  /** One entry in a {@link useConvexQueries} request map. */
4
- export type ConvexQueryRequestEntry<Query extends FunctionReference<'query'> = FunctionReference<'query'>> = {
5
+ export type ConvexQueryRequestEntry<Query extends ConvexRef<'query'> = ConvexRef<'query'>> = {
5
6
  query: Query;
6
7
  args: FunctionArgs<Query>;
8
+ /**
9
+ * Skip this entry until Convex confirms auth (`isAuthenticated`).
10
+ * Overrides the top-level {@link UseConvexQueriesOptions.authenticated}
11
+ * flag when set.
12
+ */
13
+ authenticated?: boolean;
7
14
  };
8
15
  /**
9
16
  * Request map for {@link useConvexQueries}.
10
- * Values are `{ query, args }` or `'skip'`.
17
+ * Values are `{ query, args, authenticated? }` or `'skip'`.
11
18
  */
12
19
  export type ConvexQueriesRequest = Record<string, ConvexQueryRequestEntry | 'skip'>;
13
20
  export type ConvexQueriesResult<Request extends ConvexQueriesRequest> = {
14
21
  [K in keyof Request]: Request[K] extends {
15
22
  query: infer Query;
16
23
  args: infer _Args;
17
- } ? Query extends FunctionReference<'query'> ? FunctionReturnType<Query> | undefined | Error : undefined : undefined;
24
+ } ? Query extends ConvexRef<'query'> ? FunctionReturnType<Query> | undefined | Error : undefined : undefined;
18
25
  };
26
+ export interface UseConvexQueriesOptions {
27
+ /**
28
+ * Skip every entry until Convex confirms auth (`isAuthenticated`).
29
+ * Prefer this over wrapping each value in `'skip'`. Per-entry
30
+ * `authenticated` overrides this when set.
31
+ *
32
+ * @default false
33
+ */
34
+ authenticated?: boolean;
35
+ }
19
36
  /**
20
37
  * Subscribe to a dynamic map of Convex queries (React `useQueries` parity).
21
38
  *
22
- * Pass a reactive object whose values are `{ query, args }` or `'skip'`.
23
- * Returns a computed map of the same keys → result | `undefined` (loading) |
24
- * `Error`.
39
+ * Pass a reactive object whose values are `{ query, args, authenticated? }`
40
+ * or `'skip'`. Returns a computed map of the same keys → result |
41
+ * `undefined` (loading) | `Error`.
25
42
  *
26
43
  * Browser-only live subscriptions. During SSR every entry is `undefined`
27
44
  * (combine with `useConvexQuery` when you need SSR for known queries).
28
45
  *
29
- * Unchanged keys (same function name + args) keep their existing subscription
30
- * so deep reactive churn does not tear down WebSocket watches.
46
+ * Unchanged keys (same function name + args + auth-skip) keep their
47
+ * existing subscription so deep reactive churn does not tear down
48
+ * WebSocket watches.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // Gate the whole map until Convex confirms auth
53
+ * const results = useConvexQueries(
54
+ * () => ({
55
+ * tasks: { query: api.tasks.list, args: {} },
56
+ * files: showFiles.value ? { query: api.files.list, args: {} } : 'skip',
57
+ * }),
58
+ * { authenticated: true },
59
+ * )
60
+ * ```
31
61
  */
32
- export declare function useConvexQueries<Request extends ConvexQueriesRequest>(queries: MaybeRefOrGetter<Request>): ComputedRef<ConvexQueriesResult<Request>>;
62
+ export declare function useConvexQueries<Request extends ConvexQueriesRequest>(queries: MaybeRefOrGetter<Request>, options?: UseConvexQueriesOptions): ComputedRef<ConvexQueriesResult<Request>>;
@@ -7,13 +7,18 @@ import {
7
7
  toValue,
8
8
  watch
9
9
  } from "vue";
10
+ import { resolveAuthGatedArgs } from "../utils/authGate.js";
10
11
  import { useConvexContext } from "../utils/context.js";
11
- function subscriptionSignature(query, args) {
12
- return `${getFunctionName(query)}:${JSON.stringify(convexToJson(args))}`;
12
+ function subscriptionSignature(query, args, authSkipped) {
13
+ return `${getFunctionName(query)}:${JSON.stringify(convexToJson(args))}:authSkip=${authSkipped}`;
13
14
  }
14
- export function useConvexQueries(queries) {
15
+ function entryRequiresAuth(entry, defaultAuthenticated) {
16
+ return entry.authenticated ?? defaultAuthenticated;
17
+ }
18
+ export function useConvexQueries(queries, options = {}) {
15
19
  const ctx = useConvexContext();
16
20
  const results = shallowRef({});
21
+ const defaultAuthenticated = options.authenticated ?? false;
17
22
  if (import.meta.server || !ctx.client) {
18
23
  return computed(() => results.value);
19
24
  }
@@ -29,9 +34,10 @@ export function useConvexQueries(queries) {
29
34
  results.value = rest;
30
35
  }
31
36
  };
32
- const sync = (request) => {
37
+ const sync = (request, isAuthenticated) => {
33
38
  const nextKeys = new Set(Object.keys(request));
34
- for (const key of unsubscribers.keys()) {
39
+ const known = /* @__PURE__ */ new Set([...unsubscribers.keys(), ...signatures.keys()]);
40
+ for (const key of known) {
35
41
  if (!nextKeys.has(key)) {
36
42
  dropKey(key);
37
43
  }
@@ -41,15 +47,33 @@ export function useConvexQueries(queries) {
41
47
  dropKey(key);
42
48
  continue;
43
49
  }
44
- const { query, args } = entry;
45
- const signature = subscriptionSignature(query, args);
46
- if (signatures.get(key) === signature && unsubscribers.has(key)) {
50
+ const requireAuth = entryRequiresAuth(entry, defaultAuthenticated);
51
+ const effectiveArgs = resolveAuthGatedArgs(entry.args, {
52
+ authenticated: requireAuth,
53
+ isAuthenticated
54
+ });
55
+ const authSkipped = effectiveArgs === "skip";
56
+ const signature = subscriptionSignature(
57
+ entry.query,
58
+ entry.args,
59
+ authSkipped
60
+ );
61
+ if (signatures.get(key) === signature && (unsubscribers.has(key) || authSkipped)) {
47
62
  continue;
48
63
  }
49
64
  unsubscribers.get(key)?.();
65
+ unsubscribers.delete(key);
66
+ if (authSkipped) {
67
+ signatures.set(key, signature);
68
+ if (key in results.value) {
69
+ const { [key]: _, ...rest } = results.value;
70
+ results.value = rest;
71
+ }
72
+ continue;
73
+ }
50
74
  const unsubscribe = client.onUpdate(
51
- query,
52
- args,
75
+ entry.query,
76
+ effectiveArgs,
53
77
  (value) => {
54
78
  results.value = { ...results.value, [key]: value };
55
79
  },
@@ -65,9 +89,9 @@ export function useConvexQueries(queries) {
65
89
  }
66
90
  };
67
91
  watch(
68
- () => toValue(queries),
69
- (request) => {
70
- sync(request);
92
+ () => [toValue(queries), ctx.auth.isAuthenticated.value],
93
+ ([request, isAuthenticated]) => {
94
+ sync(request, isAuthenticated);
71
95
  },
72
96
  { immediate: true, deep: true }
73
97
  );
@@ -1,6 +1,7 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
+ import type { ConvexRef } from '../utils/functionReference.js';
2
3
  import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
3
- export type ConvexQueryArgs<Query extends FunctionReference<'query'>> = FunctionArgs<Query> | 'skip';
4
+ export type ConvexQueryArgs<Query extends ConvexRef<'query'>> = FunctionArgs<Query> | 'skip';
4
5
  export interface UseConvexQueryOptions {
5
6
  /**
6
7
  * Explicit Nuxt payload/cache key.
@@ -53,4 +54,4 @@ export interface UseConvexQueryReturn<T> {
53
54
  * a duplicate HTTP request. On the browser, a live `ConvexClient` subscription
54
55
  * overlays the payload (`live ?? payload`), matching Next.js `usePreloadedQuery`.
55
56
  */
56
- export declare function useConvexQuery<Query extends FunctionReference<'query'>>(query: Query, args?: MaybeRefOrGetter<ConvexQueryArgs<Query>>, options?: UseConvexQueryOptions): Promise<UseConvexQueryReturn<FunctionReturnType<Query>>>;
57
+ export declare function useConvexQuery<Query extends ConvexRef<'query'>>(query: Query, args?: MaybeRefOrGetter<ConvexQueryArgs<Query>>, options?: UseConvexQueryOptions): Promise<UseConvexQueryReturn<FunctionReturnType<Query>>>;
@@ -1,11 +1,12 @@
1
- import type { FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionReturnType } from 'convex/server';
2
+ import type { ConvexRef } from '../utils/functionReference.js';
2
3
  /**
3
4
  * Subset of `@convex-dev/r2` `clientApi()` exports needed for client uploads.
4
5
  * Pass `api.example` (or any module that re-exports these two mutations).
5
6
  */
6
7
  export type ConvexR2UploadApi = {
7
- generateUploadUrl: FunctionReference<'mutation'>;
8
- syncMetadata: FunctionReference<'mutation'>;
8
+ generateUploadUrl: ConvexRef<'mutation'>;
9
+ syncMetadata: ConvexRef<'mutation'>;
9
10
  };
10
11
  export type ConvexR2UploadProgress = {
11
12
  loaded: number;
@@ -7,6 +7,7 @@ import {
7
7
  hydrateAuthFromStorage,
8
8
  useAuthProviderState
9
9
  } from "./composables/useAuth.js";
10
+ import { setConvexAuthModuleDefaults } from "./utils/authClientOptions.js";
10
11
  import { tryUseConvexContext } from "./utils/context.js";
11
12
  export default defineNuxtPlugin({
12
13
  name: "convex-nuxt-auth",
@@ -20,6 +21,14 @@ export default defineNuxtPlugin({
20
21
  if (!tryUseConvexContext()) {
21
22
  return;
22
23
  }
24
+ const auth = convexConfig.auth;
25
+ if (auth?.provider === "convex-auth") {
26
+ setConvexAuthModuleDefaults({
27
+ storageNamespace: auth.storageNamespace,
28
+ storage: auth.storage,
29
+ shouldHandleCode: auth.shouldHandleCode
30
+ });
31
+ }
23
32
  const pendingOAuth = hasPendingOAuthCallback();
24
33
  if (!pendingOAuth) {
25
34
  hydrateAuthFromStorage();
@@ -31,7 +40,7 @@ export default defineNuxtPlugin({
31
40
  isAuthenticated: hasSession
32
41
  });
33
42
  if (pendingOAuth) {
34
- void consumeOAuthCodeFromUrl().then((ok) => {
43
+ void consumeOAuthCodeFromUrl().catch(() => false).then((ok) => {
35
44
  if (!ok) {
36
45
  hydrateAuthFromStorage();
37
46
  }
@@ -1,5 +1,6 @@
1
- import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
1
+ import type { FunctionArgs, FunctionReturnType } from 'convex/server';
2
2
  import type { H3Event } from 'h3';
3
+ import type { ConvexRef } from '../utils/functionReference.js';
3
4
  export interface ConvexFetchOptions {
4
5
  /**
5
6
  * Deployment URL. Defaults to `NUXT_PUBLIC_CONVEX_URL` / runtimeConfig.
@@ -8,8 +9,15 @@ export interface ConvexFetchOptions {
8
9
  /**
9
10
  * JWT for this call. Prefer per-request tokens — never put JWTs in public config.
10
11
  * When omitted and `event` is passed, falls back to `convex.auth.cookie` if set.
12
+ * Ignored when {@link adminToken} is set (admin auth clears user JWT).
11
13
  */
12
14
  token?: string;
15
+ /**
16
+ * Deploy / admin key for privileged server tooling (Next.js `fetchQuery`
17
+ * parity). Calls `ConvexHttpClient.setAdminAuth`. Never put this in public
18
+ * runtime config — pass per request from a server secret.
19
+ */
20
+ adminToken?: string;
13
21
  /**
14
22
  * Nitro/H3 event. Used to resolve runtimeConfig and the optional auth cookie.
15
23
  */
@@ -30,12 +38,12 @@ export declare function resolveToken(options: ConvexFetchOptions): string | unde
30
38
  * })
31
39
  * ```
32
40
  */
33
- export declare function fetchQuery<Query extends FunctionReference<'query'>>(query: Query, args?: FunctionArgs<Query>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Query>>;
41
+ export declare function fetchQuery<Query extends ConvexRef<'query'>>(query: Query, args?: FunctionArgs<Query>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Query>>;
34
42
  /**
35
43
  * One-shot mutation via ConvexHttpClient. Use in Nitro routes / server middleware.
36
44
  */
37
- export declare function fetchMutation<Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, args?: FunctionArgs<Mutation>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Mutation>>;
45
+ export declare function fetchMutation<Mutation extends ConvexRef<'mutation'>>(mutation: Mutation, args?: FunctionArgs<Mutation>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Mutation>>;
38
46
  /**
39
47
  * One-shot action via ConvexHttpClient. Use in Nitro routes / server middleware.
40
48
  */
41
- export declare function fetchAction<Action extends FunctionReference<'action'>>(action: Action, args?: FunctionArgs<Action>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Action>>;
49
+ export declare function fetchAction<Action extends ConvexRef<'action'>>(action: Action, args?: FunctionArgs<Action>, options?: ConvexFetchOptions): Promise<FunctionReturnType<Action>>;
@@ -32,9 +32,11 @@ export function resolveToken(options) {
32
32
  });
33
33
  }
34
34
  function setupClient(options = {}) {
35
+ const token = options.adminToken ? void 0 : resolveToken(options);
35
36
  return createHttpClient(resolveUrl(options), {
36
- token: resolveToken(options),
37
- skipConvexDeploymentUrlCheck: options.skipConvexDeploymentUrlCheck
37
+ ...token ? { token } : {},
38
+ ...options.adminToken ? { adminToken: options.adminToken } : {},
39
+ ...options.skipConvexDeploymentUrlCheck !== void 0 ? { skipConvexDeploymentUrlCheck: options.skipConvexDeploymentUrlCheck } : {}
38
40
  });
39
41
  }
40
42
  export async function fetchQuery(query, args, options) {
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Runtime Convex Auth client knobs (React `ConvexAuthProvider` parity).
3
+ *
4
+ * Serializable defaults also live on `convex.auth` module options
5
+ * (`storageNamespace`, `storage: 'localStorage' | 'inMemory'`,
6
+ * `shouldHandleCode: boolean`). Call {@link configureConvexAuth} from a
7
+ * client plugin (`enforce: 'pre'`) for `replaceURL`, function
8
+ * `shouldHandleCode`, or a custom {@link ConvexAuthTokenStorage}.
9
+ */
10
+ export type ConvexAuthTokenStorage = {
11
+ getItem: (key: string) => string | undefined | null | Promise<string | undefined | null>;
12
+ setItem: (key: string, value: string) => void | Promise<void>;
13
+ removeItem: (key: string) => void | Promise<void>;
14
+ };
15
+ export type ConvexAuthStorageMode = 'localStorage' | 'inMemory';
16
+ export type ConvexAuthClientOptions = {
17
+ /**
18
+ * When `false` (or a function returning false), ignore `?code=` OAuth
19
+ * callbacks. Defaults to handling the code when a verifier is stored.
20
+ */
21
+ shouldHandleCode?: boolean | (() => boolean);
22
+ /**
23
+ * Replace the URL after stripping `?code=` (JS routers). Defaults to
24
+ * `history.replaceState`.
25
+ */
26
+ replaceURL?: (relativeUrl: string) => void | Promise<void>;
27
+ /**
28
+ * Token storage. `'localStorage'` (default), `'inMemory'`, or a custom
29
+ * {@link ConvexAuthTokenStorage} (e.g. `sessionStorage`, Secure Store).
30
+ */
31
+ storage?: ConvexAuthStorageMode | ConvexAuthTokenStorage;
32
+ /**
33
+ * Namespace for storage keys (non-alphanumeric chars stripped). Defaults
34
+ * to the Convex deployment URL.
35
+ */
36
+ storageNamespace?: string;
37
+ };
38
+ /** Serializable subset safe for `runtimeConfig.public.convex.auth`. */
39
+ export type ConvexAuthModuleClientOptions = {
40
+ shouldHandleCode?: boolean;
41
+ storage?: ConvexAuthStorageMode;
42
+ storageNamespace?: string;
43
+ };
44
+ /**
45
+ * Set runtime Convex Auth knobs. Merge is shallow — later calls override
46
+ * earlier keys. Prefer calling from a client plugin with `enforce: 'pre'`
47
+ * so options land before the auth plugin consumes OAuth codes.
48
+ */
49
+ export declare function configureConvexAuth(options: ConvexAuthClientOptions): void;
50
+ /** @internal */
51
+ export declare function setConvexAuthModuleDefaults(options: ConvexAuthModuleClientOptions): void;
52
+ /** Merged module defaults + {@link configureConvexAuth} overrides. */
53
+ export declare function getConvexAuthClientOptions(): ConvexAuthClientOptions;
54
+ /** @internal */
55
+ export declare function resetConvexAuthClientOptionsForTests(): void;
56
+ /** @internal */
57
+ export declare function createInMemoryTokenStorage(): ConvexAuthTokenStorage;
58
+ /** @internal */
59
+ export declare function resetInMemoryTokenStorageForTests(): void;
60
+ /** Resolve the active token storage from merged auth options. */
61
+ export declare function resolveAuthTokenStorage(options?: ConvexAuthClientOptions): ConvexAuthTokenStorage;
@@ -0,0 +1,73 @@
1
+ let runtimeOverrides = {};
2
+ let moduleDefaults = {};
3
+ export function configureConvexAuth(options) {
4
+ runtimeOverrides = { ...runtimeOverrides, ...options };
5
+ }
6
+ export function setConvexAuthModuleDefaults(options) {
7
+ moduleDefaults = { ...options };
8
+ }
9
+ export function getConvexAuthClientOptions() {
10
+ return {
11
+ ...moduleDefaults,
12
+ ...runtimeOverrides
13
+ };
14
+ }
15
+ export function resetConvexAuthClientOptionsForTests() {
16
+ runtimeOverrides = {};
17
+ moduleDefaults = {};
18
+ }
19
+ const inMemory = /* @__PURE__ */ new Map();
20
+ export function createInMemoryTokenStorage() {
21
+ return {
22
+ getItem: (key) => inMemory.get(key) ?? null,
23
+ setItem: (key, value) => {
24
+ inMemory.set(key, value);
25
+ },
26
+ removeItem: (key) => {
27
+ inMemory.delete(key);
28
+ }
29
+ };
30
+ }
31
+ export function resetInMemoryTokenStorageForTests() {
32
+ inMemory.clear();
33
+ }
34
+ const localStorageAdapter = {
35
+ getItem: (key) => {
36
+ if (typeof window === "undefined") {
37
+ return null;
38
+ }
39
+ try {
40
+ return window.localStorage.getItem(key);
41
+ } catch {
42
+ return null;
43
+ }
44
+ },
45
+ setItem: (key, value) => {
46
+ if (typeof window === "undefined") {
47
+ return;
48
+ }
49
+ try {
50
+ window.localStorage.setItem(key, value);
51
+ } catch {
52
+ }
53
+ },
54
+ removeItem: (key) => {
55
+ if (typeof window === "undefined") {
56
+ return;
57
+ }
58
+ try {
59
+ window.localStorage.removeItem(key);
60
+ } catch {
61
+ }
62
+ }
63
+ };
64
+ export function resolveAuthTokenStorage(options = getConvexAuthClientOptions()) {
65
+ const storage = options.storage;
66
+ if (storage === "inMemory") {
67
+ return createInMemoryTokenStorage();
68
+ }
69
+ if (storage && typeof storage === "object") {
70
+ return storage;
71
+ }
72
+ return localStorageAdapter;
73
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Convex Auth client storage helpers (mirrors @convex-dev/auth/react).
3
3
  */
4
+ import { type ConvexAuthStorageMode, type ConvexAuthTokenStorage } from './authClientOptions.js';
4
5
  export declare const JWT_STORAGE_KEY = "__convexAuthJWT";
5
6
  export declare const REFRESH_TOKEN_STORAGE_KEY = "__convexAuthRefreshToken";
6
7
  export declare const VERIFIER_STORAGE_KEY = "__convexAuthOAuthVerifier";
@@ -24,9 +25,26 @@ export type ConvexAuthConfig = {
24
25
  httpOnly?: boolean;
25
26
  /** Readable presence cookie when `httpOnly` is set (UI only). */
26
27
  presentCookie?: string;
28
+ /**
29
+ * Namespace for local token keys (React `storageNamespace` parity).
30
+ * Non-alphanumeric characters are stripped. Defaults to the deployment URL.
31
+ */
32
+ storageNamespace?: string;
33
+ /**
34
+ * Client token storage mode. Use {@link configureConvexAuth} for a custom
35
+ * {@link ConvexAuthTokenStorage}. Ignored when `httpOnly` is enabled.
36
+ * @default 'localStorage'
37
+ */
38
+ storage?: ConvexAuthStorageMode;
39
+ /**
40
+ * When `false`, do not consume `?code=` OAuth callbacks. For a function
41
+ * gate, call {@link configureConvexAuth} with `shouldHandleCode`.
42
+ */
43
+ shouldHandleCode?: boolean;
27
44
  };
28
- export declare function storageNamespace(url: string): string;
29
- export declare function storageKey(base: string, url: string): string;
45
+ export type { ConvexAuthTokenStorage, ConvexAuthStorageMode };
46
+ export declare function storageNamespace(urlOrNamespace: string): string;
47
+ export declare function storageKey(base: string, urlOrNamespace: string): string;
30
48
  /**
31
49
  * Flatten FormData (or pass through a plain record) the same way the React
32
50
  * Convex Auth client does before calling `auth:signIn`.
@@ -43,13 +61,36 @@ export declare function resolveAuthRefreshCookieName(auth?: ConvexAuthConfig): s
43
61
  export declare function isHttpOnlyAuth(auth?: ConvexAuthConfig): boolean;
44
62
  /** Empty / missing cookies are not a session. */
45
63
  export declare function cookieValueToSsrToken(value: string | null | undefined): string | undefined;
64
+ /**
65
+ * Read a stored value.
66
+ * Sync storage returns `string | null`. Async {@link ConvexAuthTokenStorage}
67
+ * returns a Promise — callers that can wait should use {@link readLocalAsync}.
68
+ */
69
+ export declare function readStored(key: string): string | null | Promise<string | null>;
70
+ /** Sync read. Returns null when storage is async (use {@link readLocalAsync}). */
46
71
  export declare function readLocal(key: string): string | null;
72
+ export declare function readLocalAsync(key: string): Promise<string | null>;
47
73
  export declare function writeLocal(key: string, value: string | null): void;
74
+ export declare function writeLocalAsync(key: string, value: string | null): Promise<void>;
75
+ /**
76
+ * `storage: 'inMemory'` cannot survive the IdP redirect, so the OAuth
77
+ * verifier is kept in a short-lived cookie (Next.js in-memory parity).
78
+ * JWTs stay in memory. Other storage modes keep the verifier with the tokens.
79
+ */
80
+ export declare function readVerifier(key: string): string | null | Promise<string | null>;
81
+ export declare function readVerifierAsync(key: string): Promise<string | null>;
82
+ export declare function writeVerifier(key: string, value: string | null): Promise<void>;
83
+ /** @internal */
84
+ export declare function readBrowserCookie(name: string): string | null;
85
+ /** @internal */
86
+ export declare function writeBrowserCookie(name: string, value: string | null, maxAgeSeconds: number): void;
48
87
  /**
49
88
  * Whether the current URL's `?code=` should be treated as an OAuth callback.
50
- * Only true when we previously stored a verifier (we started the flow).
89
+ * Requires a stored verifier (we started the flow). Honors
90
+ * `shouldHandleCode` from module options / {@link configureConvexAuth}.
51
91
  */
52
92
  export declare function shouldConsumeOAuthCode(options: {
53
93
  code: string | null;
54
94
  verifier: string | null;
95
+ shouldHandleCode?: boolean | (() => boolean);
55
96
  }): boolean;
@@ -1,3 +1,7 @@
1
+ import {
2
+ getConvexAuthClientOptions,
3
+ resolveAuthTokenStorage
4
+ } from "./authClientOptions.js";
1
5
  export const JWT_STORAGE_KEY = "__convexAuthJWT";
2
6
  export const REFRESH_TOKEN_STORAGE_KEY = "__convexAuthRefreshToken";
3
7
  export const VERIFIER_STORAGE_KEY = "__convexAuthOAuthVerifier";
@@ -6,11 +10,11 @@ export const DEFAULT_CONVEX_AUTH_COOKIE = "convex_jwt";
6
10
  export const DEFAULT_AUTH_PRESENT_COOKIE = "convex_auth_present";
7
11
  export const HTTPONLY_JWT_COOKIE = "__convexAuthJWT";
8
12
  export const HTTPONLY_REFRESH_COOKIE = "__convexAuthRefreshToken";
9
- export function storageNamespace(url) {
10
- return url.replace(/[^a-z0-9]/gi, "");
13
+ export function storageNamespace(urlOrNamespace) {
14
+ return urlOrNamespace.replace(/[^a-z0-9]/gi, "");
11
15
  }
12
- export function storageKey(base, url) {
13
- return `${base}_${storageNamespace(url)}`;
16
+ export function storageKey(base, urlOrNamespace) {
17
+ return `${base}_${storageNamespace(urlOrNamespace)}`;
14
18
  }
15
19
  export function flattenSignInParams(params) {
16
20
  if (params === void 0) {
@@ -57,29 +61,96 @@ export function isHttpOnlyAuth(auth) {
57
61
  export function cookieValueToSsrToken(value) {
58
62
  return value || void 0;
59
63
  }
60
- export function readLocal(key) {
61
- if (typeof window === "undefined") {
62
- return null;
64
+ const OAUTH_VERIFIER_MAX_AGE_SECONDS = 60 * 10;
65
+ function activeStorage() {
66
+ return resolveAuthTokenStorage(getConvexAuthClientOptions());
67
+ }
68
+ function authStorageIsInMemory() {
69
+ return getConvexAuthClientOptions().storage === "inMemory";
70
+ }
71
+ export function readStored(key) {
72
+ const value = activeStorage().getItem(key);
73
+ if (value instanceof Promise) {
74
+ return value.then((stored) => stored ?? null);
63
75
  }
64
- try {
65
- return window.localStorage.getItem(key);
66
- } catch {
76
+ return value ?? null;
77
+ }
78
+ export function readLocal(key) {
79
+ const value = readStored(key);
80
+ if (value instanceof Promise) {
67
81
  return null;
68
82
  }
83
+ return value;
84
+ }
85
+ export async function readLocalAsync(key) {
86
+ return await readStored(key);
69
87
  }
70
88
  export function writeLocal(key, value) {
71
- if (typeof window === "undefined") {
89
+ void writeLocalAsync(key, value);
90
+ }
91
+ export async function writeLocalAsync(key, value) {
92
+ const storage = activeStorage();
93
+ if (value === null) {
94
+ await storage.removeItem(key);
95
+ } else {
96
+ await storage.setItem(key, value);
97
+ }
98
+ }
99
+ export function readVerifier(key) {
100
+ if (authStorageIsInMemory()) {
101
+ return readBrowserCookie(key);
102
+ }
103
+ return readStored(key);
104
+ }
105
+ export async function readVerifierAsync(key) {
106
+ return await readVerifier(key);
107
+ }
108
+ export async function writeVerifier(key, value) {
109
+ if (authStorageIsInMemory()) {
110
+ writeBrowserCookie(key, value, OAUTH_VERIFIER_MAX_AGE_SECONDS);
72
111
  return;
73
112
  }
113
+ await writeLocalAsync(key, value);
114
+ }
115
+ function escapeRegExp(value) {
116
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
117
+ }
118
+ export function readBrowserCookie(name) {
119
+ if (typeof document === "undefined") {
120
+ return null;
121
+ }
122
+ const match = document.cookie.match(new RegExp(`(?:^|; )${escapeRegExp(name)}=([^;]*)`));
123
+ const raw = match?.[1];
124
+ if (!raw) {
125
+ return null;
126
+ }
74
127
  try {
75
- if (value === null) {
76
- window.localStorage.removeItem(key);
77
- } else {
78
- window.localStorage.setItem(key, value);
79
- }
128
+ return decodeURIComponent(raw);
80
129
  } catch {
130
+ return raw;
131
+ }
132
+ }
133
+ export function writeBrowserCookie(name, value, maxAgeSeconds) {
134
+ if (typeof document === "undefined") {
135
+ return;
136
+ }
137
+ const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
138
+ if (value === null) {
139
+ document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax${secure}`;
140
+ return;
81
141
  }
142
+ document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax${secure}`;
82
143
  }
83
144
  export function shouldConsumeOAuthCode(options) {
84
- return options.code !== null && options.verifier !== null;
145
+ if (options.code === null || options.verifier === null) {
146
+ return false;
147
+ }
148
+ const gate = options.shouldHandleCode ?? getConvexAuthClientOptions().shouldHandleCode;
149
+ if (gate === void 0) {
150
+ return true;
151
+ }
152
+ if (typeof gate === "function") {
153
+ return gate();
154
+ }
155
+ return gate;
85
156
  }
@@ -1,5 +1,6 @@
1
1
  import type { AuthTokenFetcher, ConvexClient, ConvexHttpClient } from 'convex/browser';
2
2
  import type { ComputedRef, InjectionKey, Ref } from 'vue';
3
+ import type { CreateHttpClientOptions } from './http.js';
3
4
  export interface ConvexAuthContext {
4
5
  /**
5
6
  * Whether useConvexAuth({ fetchToken }) has been called.
@@ -37,9 +38,7 @@ export interface ConvexNuxtContext {
37
38
  * Create a fresh HttpClient for one-shot fetches (SSR / Nitro / refresh).
38
39
  * Prefer a new client per call — HttpClient is stateful.
39
40
  */
40
- createHttpClient: (options?: {
41
- token?: string;
42
- }) => ConvexHttpClient;
41
+ createHttpClient: (options?: CreateHttpClientOptions) => ConvexHttpClient;
43
42
  }
44
43
  export declare const convexNuxtKey: InjectionKey<ConvexNuxtContext>;
45
44
  export declare function createAuthContext(): ConvexAuthContext;
@@ -0,0 +1,10 @@
1
+ import type { FunctionReference, FunctionReference_future, FunctionType } from 'convex/server';
2
+ /**
3
+ * Function reference accepted by use-convex client APIs.
4
+ *
5
+ * Convex 1.46+ introduced {@link FunctionReference_future} for stricter
6
+ * callback argument checking. Generated `api` refs remain plain
7
+ * {@link FunctionReference}; library surfaces accept either kind so callers
8
+ * can pass both (same contract as Convex React hooks).
9
+ */
10
+ export type ConvexRef<Type extends FunctionType> = FunctionReference<Type> | FunctionReference_future<Type>;
File without changes
@@ -1,6 +1,13 @@
1
1
  import { ConvexHttpClient } from 'convex/browser';
2
2
  export interface CreateHttpClientOptions {
3
3
  token?: string;
4
+ /**
5
+ * Deploy / admin key for privileged HttpClient calls (Next.js
6
+ * `fetchQuery(..., { adminToken })` parity). Uses `Authorization: Convex …`
7
+ * and clears any user JWT. Prefer per-request secrets — never put this in
8
+ * public runtime config.
9
+ */
10
+ adminToken?: string;
4
11
  skipConvexDeploymentUrlCheck?: boolean;
5
12
  }
6
13
  /**
@@ -2,9 +2,12 @@ import { ConvexHttpClient } from "convex/browser";
2
2
  export function createHttpClient(url, options = {}) {
3
3
  const client = new ConvexHttpClient(url, {
4
4
  skipConvexDeploymentUrlCheck: options.skipConvexDeploymentUrlCheck,
5
- ...options.token ? { auth: options.token } : {}
5
+ ...options.token && !options.adminToken ? { auth: options.token } : {}
6
6
  });
7
- if (options.token) {
7
+ if (options.adminToken) {
8
+ ;
9
+ client.setAdminAuth(options.adminToken);
10
+ } else if (options.token) {
8
11
  client.setAuth(options.token);
9
12
  }
10
13
  ;
@@ -1,8 +1,8 @@
1
- import type { FunctionReference } from 'convex/server';
1
+ import type { ConvexRef } from './functionReference.js';
2
2
  /**
3
3
  * Stable Nuxt payload / useAsyncData key for a Convex query.
4
4
  *
5
5
  * `'skip'` means "don't fetch" — it must not change the key, otherwise
6
6
  * auth-gated queries lose the SSR payload on the client.
7
7
  */
8
- export declare function convexQueryKey(query: FunctionReference<'query'>, args: unknown, explicitKey?: string): string;
8
+ export declare function convexQueryKey(query: ConvexRef<'query'>, args: unknown, explicitKey?: string): string;
package/dist/types.d.mts CHANGED
@@ -18,16 +18,20 @@ export { type ConvexR2UploadApi, type ConvexR2UploadProgress } from '../dist/run
18
18
 
19
19
  export { type PaginatedQueryArgs, type PaginatedQueryItem, type PaginatedQueryReference, type PaginationStatus, type UseConvexPaginatedQueryOptions, type UseConvexPaginatedQueryReturn } from '../dist/runtime/composables/useConvexPaginatedQuery.js'
20
20
 
21
- export { type ConvexQueriesRequest, type ConvexQueriesResult, type ConvexQueryRequestEntry } from '../dist/runtime/composables/useConvexQueries.js'
21
+ export { type ConvexQueriesRequest, type ConvexQueriesResult, type ConvexQueryRequestEntry, type UseConvexQueriesOptions } from '../dist/runtime/composables/useConvexQueries.js'
22
22
 
23
23
  export { type ConvexFetchOptions } from '../dist/runtime/server/fetch.js'
24
24
 
25
+ export { type ConvexRef } from '../dist/runtime/utils/functionReference.js'
26
+
25
27
  export { type ConvexAuthContext, type ConvexNuxtContext } from '../dist/runtime/utils/context.js'
26
28
 
27
29
  export { type ConnectionState } from '../dist/runtime/composables/useConvexConnectionState.js'
28
30
 
29
31
  export { type ConvexGate } from '../dist/runtime/composables/useConvexGate.js'
30
32
 
33
+ export { type ConvexAuthClientOptions, type ConvexAuthStorageMode, type ConvexAuthTokenStorage, type configureConvexAuth } from '../dist/runtime/utils/authClientOptions.js'
34
+
31
35
  export { default } from './module.mjs'
32
36
 
33
37
  export { type ModuleAuthOptions, type ModuleOptions, type ModulePublicRuntimeConfig } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-convex",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "First-class Convex integration for Nuxt",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,7 +50,7 @@
50
50
  "@nuxt/schema": "^4.0.0",
51
51
  "@nuxt/test-utils": "^3.23.0",
52
52
  "@types/node": "^26.5.0",
53
- "convex": "^1.45.0",
53
+ "convex": "^1.46.0",
54
54
  "convex-test": "^0.0.56",
55
55
  "happy-dom": "^20.14.0",
56
56
  "nitropack": "^2.13.0",
@@ -63,7 +63,7 @@
63
63
  "vue-tsc": "^3.0.0"
64
64
  },
65
65
  "peerDependencies": {
66
- "convex": "^1.21.0",
66
+ "convex": "^1.46.0",
67
67
  "nuxt": "^4.0.0",
68
68
  "vue": "^3.5.0"
69
69
  },