use-convex 0.0.2 → 1.1.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 (52) hide show
  1. package/README.md +115 -35
  2. package/dist/module.d.mts +5 -3
  3. package/dist/module.mjs +35 -1
  4. package/dist/runtime/composables/useAuth.d.ts +5 -5
  5. package/dist/runtime/composables/useAuth.js +11 -24
  6. package/dist/runtime/composables/useAuthToken.d.ts +1 -1
  7. package/dist/runtime/composables/useAuthToken.js +2 -10
  8. package/dist/runtime/composables/useConvex.js +1 -1
  9. package/dist/runtime/composables/useConvexAction.js +6 -18
  10. package/dist/runtime/composables/useConvexAuth.js +4 -13
  11. package/dist/runtime/composables/useConvexFileUpload.d.ts +43 -0
  12. package/dist/runtime/composables/useConvexFileUpload.js +102 -0
  13. package/dist/runtime/composables/useConvexGate.js +3 -9
  14. package/dist/runtime/composables/useConvexMutation.js +7 -17
  15. package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +1 -1
  16. package/dist/runtime/composables/useConvexPaginatedQuery.js +7 -29
  17. package/dist/runtime/composables/useConvexQueries.d.ts +12 -7
  18. package/dist/runtime/composables/useConvexQueries.js +1 -3
  19. package/dist/runtime/composables/useConvexQuery.js +6 -12
  20. package/dist/runtime/composables/useConvexR2Upload.d.ts +40 -0
  21. package/dist/runtime/composables/useConvexR2Upload.js +86 -0
  22. package/dist/runtime/plugin.client.js +3 -7
  23. package/dist/runtime/plugin.devtools.client.d.ts +15 -2
  24. package/dist/runtime/plugin.devtools.client.js +18 -4
  25. package/dist/runtime/plugin.server.js +3 -7
  26. package/dist/runtime/server/api/convex/auth/session.js +7 -12
  27. package/dist/runtime/server/auth.js +1 -9
  28. package/dist/runtime/server/authCookies.js +8 -30
  29. package/dist/runtime/server/convexConfig.d.ts +12 -0
  30. package/dist/runtime/server/convexConfig.js +9 -0
  31. package/dist/runtime/server/fetch.d.ts +2 -4
  32. package/dist/runtime/server/fetch.js +3 -13
  33. package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -2
  34. package/dist/runtime/server/routes/__convex_devtools.get.js +184 -16
  35. package/dist/runtime/server/sameOrigin.js +1 -5
  36. package/dist/runtime/utils/authCookie.d.ts +0 -2
  37. package/dist/runtime/utils/authCookie.js +0 -1
  38. package/dist/runtime/utils/authState.js +1 -6
  39. package/dist/runtime/utils/authStorage.js +1 -1
  40. package/dist/runtime/utils/context.js +3 -2
  41. package/dist/runtime/utils/convexDashboard.d.ts +44 -0
  42. package/dist/runtime/utils/convexDashboard.js +77 -0
  43. package/dist/runtime/utils/errors.d.ts +14 -0
  44. package/dist/runtime/utils/errors.js +25 -0
  45. package/dist/runtime/utils/oauthRedirect.js +3 -2
  46. package/dist/runtime/utils/paginatedOptimistic.js +4 -22
  47. package/dist/runtime/utils/payloadCache.d.ts +6 -0
  48. package/dist/runtime/utils/payloadCache.js +7 -0
  49. package/dist/runtime/utils/pendingError.d.ts +9 -0
  50. package/dist/runtime/utils/pendingError.js +23 -0
  51. package/dist/types.d.mts +7 -3
  52. package/package.json +36 -20
package/README.md CHANGED
@@ -6,8 +6,10 @@ SSR snapshots hydrate through the Nuxt payload, then the browser overlays a live
6
6
 
7
7
  ## Install
8
8
 
9
+ [`use-convex` on npm](https://www.npmjs.com/package/use-convex)
10
+
9
11
  ```bash
10
- pnpm add use-convex convex
12
+ npm i use-convex convex
11
13
  ```
12
14
 
13
15
  ```ts
@@ -24,7 +26,7 @@ export default defineNuxtConfig({
24
26
  <script setup lang="ts">
25
27
  import { api } from '~~/convex/_generated/api'
26
28
 
27
- const { data, pending, error } = await useConvexQuery(api.tasks.list, {})
29
+ const { data, pending, error } = await useConvexQuery(api.tasks.list, {}, { authenticated: true })
28
30
  </script>
29
31
  ```
30
32
 
@@ -56,17 +58,18 @@ Set `convex.server: false` to skip SSR snapshots globally, or pass `{ server: fa
56
58
  const { data, pending, error, refresh } = await useConvexQuery(
57
59
  api.tasks.list,
58
60
  {}, // args, a ref / getter, or 'skip'
61
+ { authenticated: true }, // wait for Convex auth before live subscribe
59
62
  )
60
63
  ```
61
64
 
62
- | Option | Default | Purpose |
63
- |---|---|---|
64
- | `key` | function name + args | Nuxt payload / cache key |
65
- | `server` | `convex.server` (`true`) | SSR HttpClient snapshot |
66
- | `lazy` | `false` | Non-blocking on client navigation |
67
- | `live` | `true` | Subscribe after hydration |
68
- | `authenticated` | `false` | Wait for Convex auth before live subscribe |
69
- | `token` | cookie / none | Per-request JWT for SSR |
65
+ | Option | Default | Purpose |
66
+ | --------------- | ------------------------ | ------------------------------------------ |
67
+ | `key` | function name + args | Nuxt payload / cache key |
68
+ | `server` | `convex.server` (`true`) | SSR HttpClient snapshot |
69
+ | `lazy` | `false` | Non-blocking on client navigation |
70
+ | `live` | `true` | Subscribe after hydration |
71
+ | `authenticated` | `false` | Wait for Convex auth before live subscribe |
72
+ | `token` | cookie / none | Per-request JWT for SSR |
70
73
 
71
74
  Args accept the query's `FunctionArgs`, `'skip'`, or a `MaybeRefOrGetter` of either.
72
75
 
@@ -82,13 +85,14 @@ prewarmQuery(api.tasks.list, {})
82
85
 
83
86
  ### Several queries
84
87
 
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
+
85
90
  ```ts
86
91
  const results = useConvexQueries(() => ({
87
- inbox: { query: api.messages.list, args: { channel: 'inbox' } },
88
- later: selectedId.value
89
- ? { query: api.messages.get, args: { id: selectedId.value } }
90
- : 'skip',
92
+ tasks: { query: api.tasks.list, args: {} },
93
+ files: showFiles.value ? { query: api.files.list, args: {} } : 'skip',
91
94
  }))
95
+ // results.value.tasks
92
96
  ```
93
97
 
94
98
  ### Pagination
@@ -97,8 +101,9 @@ const results = useConvexQueries(() => ({
97
101
  const { results, status, isLoading, loadMore } = await useConvexPaginatedQuery(
98
102
  api.tasks.listPaginated,
99
103
  {},
100
- { initialNumItems: 20 },
104
+ { initialNumItems: 20, authenticated: true },
101
105
  )
106
+ // loadMore() when status === 'CanLoadMore'
102
107
  ```
103
108
 
104
109
  The first page is SSR'd. On the browser, every loaded page stays live. `loadMore` fetches the next page.
@@ -115,10 +120,10 @@ const { mutate, pending, error } = useConvexMutation(api.tasks.create, {
115
120
  ])
116
121
  },
117
122
  })
118
- await mutate({ text: 'Ship it' })
123
+ await mutate({ text: 'Ship it' }) // browser-only
119
124
 
120
- const { run } = useConvexAction(api.ai.summarize)
121
- await run({ text: '' })
125
+ const { run, pending, error } = useConvexAction(api.tasks.shout)
126
+ const shouted = await run({ text: 'hello' }) // browser-only
122
127
  ```
123
128
 
124
129
  For paginated lists:
@@ -133,7 +138,48 @@ insertAtTop({
133
138
 
134
139
  Also available: `insertAtBottomIfLoaded`, `insertAtPosition`, `optimisticallyUpdateValueInPaginatedQuery`.
135
140
 
136
- `useConvex()` returns the browser `ConvexClient` when you need an escape hatch. `useConvexConnectionState()` is a reactive WebSocket `ConnectionState`.
141
+ `useConvex()` returns the browser `ConvexClient` when you need an escape hatch. `useConvexConnectionState()` is a reactive `ShallowRef<ConnectionState | null>` (WebSocket status: `isWebSocketConnected`, `hasInflightRequests`, …).
142
+
143
+ ## File uploads
144
+
145
+ Convex file storage is a three-step client flow: generate a short-lived upload URL, `POST` the file bytes, then save the returned `storageId` in a mutation. `useConvexFileUpload` wraps that for Nuxt (browser-only, with `pending` / `error` / `progress`).
146
+
147
+ ```ts
148
+ const { upload, pending, error, progress } = useConvexFileUpload({
149
+ generateUploadUrl: api.files.generateUploadUrl,
150
+ saveFile: api.files.save, // ({ storageId, name, contentType, size, ... }) => Id<"files">
151
+ })
152
+
153
+ await upload(file) // progress: 0..1 while bytes fly
154
+ // optional extra save args: await upload(file, { caption: '…' })
155
+ ```
156
+
157
+ Your Convex mutations must enforce auth — never expose an unauthenticated `generateUploadUrl`. Prefer storing `storageId` (and resolving URLs with `ctx.storage.getUrl`) over persisting raw public URLs. See the playground `files` module and the `/files` page for a full list/upload/delete example.
158
+
159
+ ### Cloudflare R2 (`@convex-dev/r2`)
160
+
161
+ For larger objects or R2-backed apps, use `useConvexR2Upload` — the Vue counterpart of `@convex-dev/r2/react`'s `useUploadFile`. It does not depend on the R2 package at runtime; pass the `clientApi()` exports from your Convex app:
162
+
163
+ ```ts
164
+ // convex/r2.ts
165
+ import { R2 } from '@convex-dev/r2'
166
+ import { components } from './_generated/api'
167
+
168
+ const r2 = new R2(components.r2)
169
+ export const { generateUploadUrl, syncMetadata } = r2.clientApi({
170
+ checkUpload: async (ctx) => {
171
+ /* auth */
172
+ },
173
+ })
174
+ ```
175
+
176
+ ```ts
177
+ // Pass r2.clientApi() exports: { generateUploadUrl, syncMetadata }
178
+ const { upload, pending, error, progress } = useConvexR2Upload(api.r2)
179
+ const key = await upload(file) // R2 object key
180
+ ```
181
+
182
+ That runs `generateUploadUrl` → `PUT` to the signed URL → `syncMetadata({ key })`, with XHR progress. Use built-in `useConvexFileUpload` for Convex storage; use this helper when you adopt the R2 component.
137
183
 
138
184
  ## Auth
139
185
 
@@ -158,6 +204,7 @@ const { signIn, signOut } = useAuth()
158
204
 
159
205
  <template>
160
206
  <AuthLoading>Resolving…</AuthLoading>
207
+ <AuthRefreshing>Refreshing session…</AuthRefreshing>
161
208
  <Authenticated>
162
209
  <!-- signed-in shell -->
163
210
  </Authenticated>
@@ -167,7 +214,7 @@ const { signIn, signOut } = useAuth()
167
214
  </template>
168
215
  ```
169
216
 
170
- Prefer `<Authenticated>` (or `showAuthedUi`) so SSR HTML does not flash the sign-in form. Gate private queries with `{ authenticated: true }`.
217
+ Prefer `<Authenticated>` (or `showAuthedUi`) so SSR HTML does not flash the sign-in form. Gate private queries with `{ authenticated: true }`. `<AuthRefreshing>` shows only while an authenticated session is refreshing a rejected token (same as React).
171
218
 
172
219
  ```ts
173
220
  await signIn('password', { email, password, flow: 'signIn' })
@@ -214,7 +261,7 @@ export default defineNuxtRouteMiddleware(() => {
214
261
  })
215
262
  ```
216
263
 
217
- `useAuthToken()` returns the current JWT for authenticated HTTP calls. `useConvexGate()` exposes `{ showAuthedUi, showLoading, showSignedOut, showRefreshing }` if you prefer flags over layout components.
264
+ `useAuthToken()` returns the current JWT for authenticated HTTP calls. `useConvexGate()` exposes `{ showAuthedUi, showLoading, showSignedOut, showRefreshing }` if you prefer flags over layout components (`Authenticated` / `Unauthenticated` / `AuthLoading` / `AuthRefreshing`).
218
265
 
219
266
  ### Bring your own (Clerk, Auth0, custom)
220
267
 
@@ -236,7 +283,9 @@ const { data } = await useConvexQuery(api.tasks.list, {}, { authenticated: true
236
283
  Optional SSR cookie (you must write the JWT after sign-in):
237
284
 
238
285
  ```ts
239
- auth: { cookie: 'convex_jwt' }
286
+ auth: {
287
+ cookie: 'convex_jwt'
288
+ }
240
289
  ```
241
290
 
242
291
  ## Server routes
@@ -260,6 +309,15 @@ export default defineEventHandler(async (event) => {
260
309
  })
261
310
  ```
262
311
 
312
+ ```ts
313
+ // server/api/shout.post.ts
314
+ export default defineEventHandler(async (event) => {
315
+ requireConvexAuth(event)
316
+ const { text } = await readBody(event)
317
+ return await fetchAction(api.tasks.shout, { text }, { event })
318
+ })
319
+ ```
320
+
263
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.
264
322
 
265
323
  ## Config
@@ -280,6 +338,19 @@ convex: {
280
338
 
281
339
  `url` also reads `NUXT_PUBLIC_CONVEX_URL` via `runtimeConfig.public.convex.url`, so you can change the deployment without rebuilding.
282
340
 
341
+ ## Nuxt DevTools
342
+
343
+ In development, a **Convex** tab appears in [Nuxt DevTools](https://devtools.nuxt.com). It embeds the official [hosted dashboard](https://docs.convex.dev/platform-apis/embedded-dashboard) for `*.convex.cloud` URLs and shows module config / tips.
344
+
345
+ - **Open dashboard** deep-links to `dashboard.convex.dev` (uses your existing Convex login).
346
+ - **Auto-login** in the embed needs `CONVEX_DEPLOY_KEY` in the Nuxt process environment (e.g. `.env.local`). Without it, the iframe shows Convex’s credential form.
347
+ - A deploy key inlined into the DevTools page is visible to anyone who can reach your local `nuxt dev` server — only set it for trusted local machines.
348
+ - Local / self-hosted backends are not embedded; use the Open dashboard link or the CLI dashboard instead.
349
+
350
+ ## Contributing
351
+
352
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). Lint/format/test use **Vite+** (`vp`) — Oxlint, Oxfmt, and Vitest — while Nuxt module build/dev stay on `nuxt-module-build` / `nuxi`.
353
+
283
354
  ## Playground
284
355
 
285
356
  `playground/` is a normal Nuxt app that consumes this module (workspace-linked).
@@ -287,7 +358,7 @@ convex: {
287
358
  ```bash
288
359
  pnpm install
289
360
 
290
- # Terminal 1 — Convex backend (writes NUXT_PUBLIC_CONVEX_URL to .env.local)
361
+ # Terminal 1 — Convex backend (writes CONVEX_URL to playground/.env.local)
291
362
  pnpm run dev:backend
292
363
  # first time: npx @convex-dev/auth # JWT_PRIVATE_KEY + JWKS
293
364
 
@@ -295,27 +366,36 @@ pnpm run dev:backend
295
366
  pnpm run dev
296
367
  ```
297
368
 
298
- | Route | What it shows |
299
- |---|---|
300
- | `/` | SSR snapshot + live overlay (sign up, CRUD todos) |
301
- | `/server` | Nitro `fetchQuery` / `fetchMutation` / `fetchAction` |
302
- | `/extras` | `live: false`, pagination, action, connection state |
369
+ | Route | What it shows |
370
+ | --------- | ----------------------------------------------------- |
371
+ | `/` | Shell session: features + composable call-shape demos |
372
+ | `/live` | SSR snapshot + live overlay (sign up, CRUD todos) |
373
+ | `/server` | Nitro `fetchQuery` / `fetchMutation` / `fetchAction` |
374
+ | `/files` | `useConvexFileUpload` (upload, list, preview, delete) |
375
+ | `/extras` | `live: false`, pagination, action, connection state |
303
376
 
304
377
  On `/server`: `GET /api/health` is public; `GET`/`POST /api/tasks` use the cookie JWT; `POST /api/shout` is a public `fetchAction` demo.
305
378
 
306
379
  ## Releasing
307
380
 
308
- Releases run from `.github/workflows/release.yml` when commits land on `main`. Version bumps follow [conventional commits](https://www.conventionalcommits.org/):
381
+ Releases run from `.github/workflows/release.yml` when commits that touch `src/` land on `main` (or when the workflow is run manually). Version bumps follow [conventional commits](https://www.conventionalcommits.org/):
309
382
 
310
- | Commit | Release |
311
- |---|---|
312
- | `fix:` | patch |
313
- | `feat:` | minor |
314
- | `feat!:` or `BREAKING CHANGE:` | major |
315
- | `chore:`, `docs:`, `ci:`, … | none |
383
+ | Commit | Release |
384
+ | ------------------------------ | ------- |
385
+ | `feat!:` or `BREAKING CHANGE:` | major |
386
+ | `feat:` | minor |
387
+ | anything else under `src/` | patch |
316
388
 
317
389
  semantic-release publishes `use-convex` to npm, tags `vX.Y.Z`, and opens a GitHub Release. The repo `package.json` version is not committed back.
318
390
 
391
+ Publishing uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC), not an `NPM_TOKEN`. On [the `use-convex` package settings](https://www.npmjs.com/package/use-convex?activeTab=settings) add a GitHub Actions trusted publisher:
392
+
393
+ - **Organization or user:** `jrmybtlr`
394
+ - **Repository:** `convex-nuxt`
395
+ - **Workflow filename:** `release.yml`
396
+ - **Environment:** leave empty
397
+ - **Allowed actions:** include **`npm publish`** (new publishers default to staged publish only)
398
+
319
399
  ## License
320
400
 
321
401
  MIT
package/dist/module.d.mts CHANGED
@@ -3,9 +3,11 @@ import * as convex_browser from 'convex/browser';
3
3
  export { AuthTokens, SignInResult, UseAuthReturn } from '../dist/runtime/composables/useAuth.js';
4
4
  export { ConvexQueryArgs, UseConvexQueryOptions, UseConvexQueryReturn } from '../dist/runtime/composables/useConvexQuery.js';
5
5
  export { AuthTokenFetcher, UseConvexAuthReturn, UseConvexAuthSetupOptions } from '../dist/runtime/composables/useConvexAuth.js';
6
- export { UseConvexMutationOptions } from '../dist/runtime/composables/useConvexMutation.js';
7
- export { PaginatedQueryReference, PaginationStatus, UseConvexPaginatedQueryOptions, UseConvexPaginatedQueryReturn } from '../dist/runtime/composables/useConvexPaginatedQuery.js';
8
- export { ConvexQueriesRequest, ConvexQueriesResult } from '../dist/runtime/composables/useConvexQueries.js';
6
+ export { OptimisticUpdate, UseConvexMutationOptions } from '../dist/runtime/composables/useConvexMutation.js';
7
+ export { ConvexFileUploadExtraArgs, ConvexFileUploadMeta, UseConvexFileUploadOptions } from '../dist/runtime/composables/useConvexFileUpload.js';
8
+ export { ConvexR2UploadApi, ConvexR2UploadProgress } from '../dist/runtime/composables/useConvexR2Upload.js';
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';
9
11
  export { ConvexFetchOptions } from '../dist/runtime/server/fetch.js';
10
12
  export { ConvexAuthContext, ConvexNuxtContext } from '../dist/runtime/utils/context.js';
11
13
  export { ConnectionState } from '../dist/runtime/composables/useConvexConnectionState.js';
package/dist/module.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { defineNuxtModule, createResolver, addPlugin, addComponent, addServerScanDir, addServerHandler, addImports, addServerImports, addTypeTemplate } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
  import { resolveAuthCookieName } from '../dist/runtime/utils/authStorage.js';
4
+ import { suggestedCloudUrlFromMismatch } from '../dist/runtime/utils/convexDashboard.js';
5
+ import { warnStaleLocalConvexUrl } from '../dist/runtime/utils/errors.js';
4
6
 
5
7
  const module$1 = defineNuxtModule({
6
8
  meta: {
@@ -26,6 +28,25 @@ const module$1 = defineNuxtModule({
26
28
  client: options.client,
27
29
  auth
28
30
  });
31
+ if (nuxt.options.dev) {
32
+ const publicUrl = nuxt.options.runtimeConfig.public.convex?.url ?? "";
33
+ const suggestedCloudUrl = suggestedCloudUrlFromMismatch(
34
+ publicUrl,
35
+ process.env.CONVEX_DEPLOYMENT
36
+ );
37
+ if (suggestedCloudUrl) {
38
+ warnStaleLocalConvexUrl(publicUrl, suggestedCloudUrl);
39
+ }
40
+ nuxt.options.runtimeConfig.convexDevtools = defu(
41
+ nuxt.options.runtimeConfig.convexDevtools ?? {},
42
+ {
43
+ deployKey: process.env.CONVEX_DEPLOY_KEY ?? "",
44
+ deployment: process.env.CONVEX_DEPLOYMENT ?? "",
45
+ // Fallback when public.convex.url was empty at config eval time.
46
+ url: process.env.NUXT_PUBLIC_CONVEX_URL ?? process.env.CONVEX_URL ?? ""
47
+ }
48
+ );
49
+ }
29
50
  const resolver = createResolver(import.meta.url);
30
51
  const runtimeDir = resolver.resolve("./runtime");
31
52
  const useConvexAuthProvider = auth?.provider === "convex-auth";
@@ -57,7 +78,12 @@ const module$1 = defineNuxtModule({
57
78
  mode: "client"
58
79
  });
59
80
  }
60
- for (const name of ["Authenticated", "Unauthenticated", "AuthLoading"]) {
81
+ for (const name of [
82
+ "Authenticated",
83
+ "Unauthenticated",
84
+ "AuthLoading",
85
+ "AuthRefreshing"
86
+ ]) {
61
87
  addComponent({
62
88
  name,
63
89
  filePath: resolver.resolve(`./runtime/components/${name}.vue`)
@@ -103,6 +129,14 @@ const module$1 = defineNuxtModule({
103
129
  name: "useConvexMutation",
104
130
  from: resolver.resolve("./runtime/composables/useConvexMutation")
105
131
  },
132
+ {
133
+ name: "useConvexFileUpload",
134
+ from: resolver.resolve("./runtime/composables/useConvexFileUpload")
135
+ },
136
+ {
137
+ name: "useConvexR2Upload",
138
+ from: resolver.resolve("./runtime/composables/useConvexR2Upload")
139
+ },
106
140
  {
107
141
  name: "useConvexAction",
108
142
  from: resolver.resolve("./runtime/composables/useConvexAction")
@@ -43,17 +43,17 @@ export declare function signIn(provider?: string, params?: FormData | Record<str
43
43
  * Sign out and clear local tokens + SSR JWT cookie.
44
44
  */
45
45
  export declare function signOut(): Promise<void>;
46
- /** @internal Used by the Convex Auth client plugin. */
46
+ /** @internal */
47
47
  export declare function hydrateAuthFromStorage(): void;
48
- /** @internal Token fetcher for `useConvexAuth({ fetchToken })`. */
48
+ /** @internal */
49
49
  export declare function getAuthToken({ forceRefreshToken, }: {
50
50
  forceRefreshToken: boolean;
51
51
  }): Promise<string | null>;
52
- /** @internal Sync check for OAuth `?code=` + stored verifier (no await). */
52
+ /** @internal */
53
53
  export declare function hasPendingOAuthCallback(): boolean;
54
- /** @internal Used by the Convex Auth client plugin. */
54
+ /** @internal */
55
55
  export declare function consumeOAuthCodeFromUrl(): Promise<boolean>;
56
- /** @internal Provider session flags for `useConvexAuth`. */
56
+ /** @internal */
57
57
  export declare function useAuthProviderState(): {
58
58
  isLoading: ComputedRef<boolean>;
59
59
  hasSession: ComputedRef<boolean>;
@@ -1,19 +1,11 @@
1
1
  import { ConvexHttpClient } from "convex/browser";
2
2
  import { makeFunctionReference } from "convex/server";
3
- import {
4
- computed,
5
- nextTick
6
- } from "vue";
7
- import {
8
- useRuntimeConfig,
9
- useState
10
- } from "nuxt/app";
3
+ import { computed, nextTick } from "vue";
4
+ import { useRuntimeConfig, useState } from "nuxt/app";
11
5
  import { useConvexAuth } from "./useConvexAuth.js";
6
+ import { missingConvexUrlError, unreachableConvexUrlError } from "../utils/errors.js";
12
7
  import { withRefreshMutex } from "../utils/authMutex.js";
13
- import {
14
- useAuthJwtCookie,
15
- useAuthPresentCookie
16
- } from "../utils/authCookie.js";
8
+ import { useAuthJwtCookie, useAuthPresentCookie } from "../utils/authCookie.js";
17
9
  import {
18
10
  flattenSignInParams,
19
11
  isHttpOnlyAuth,
@@ -28,9 +20,7 @@ import {
28
20
  import { tryUseConvexContext } from "../utils/context.js";
29
21
  import { parseOAuthRedirect } from "../utils/oauthRedirect.js";
30
22
  const authSignIn = makeFunctionReference("auth:signIn");
31
- const authSignOut = makeFunctionReference(
32
- "auth:signOut"
33
- );
23
+ const authSignOut = makeFunctionReference("auth:signOut");
34
24
  const RETRY_BACKOFF = [500, 2e3];
35
25
  const RETRY_JITTER = 100;
36
26
  export function useAuth() {
@@ -65,7 +55,7 @@ export async function signIn(provider, params) {
65
55
  const session = useAuthSession();
66
56
  const convexUrl = session.convexUrl;
67
57
  if (!convexUrl) {
68
- throw new Error("Convex URL is not configured");
58
+ throw missingConvexUrlError("signIn");
69
59
  }
70
60
  session.pending.value = true;
71
61
  session.error.value = null;
@@ -94,7 +84,7 @@ export async function signIn(provider, params) {
94
84
  }
95
85
  return { signingIn: false };
96
86
  } catch (cause) {
97
- const message = cause instanceof Error ? cause.message : "Authentication failed";
87
+ const message = isNetworkError(cause) && convexUrl ? unreachableConvexUrlError(convexUrl).message : cause instanceof Error ? cause.message : "Authentication failed";
98
88
  session.error.value = message;
99
89
  throw cause;
100
90
  } finally {
@@ -319,13 +309,10 @@ async function fetchAuthSession() {
319
309
  }
320
310
  async function fetchAuthSessionToken() {
321
311
  try {
322
- return await $fetch(
323
- AUTH_SESSION_PATH,
324
- {
325
- method: "POST",
326
- body: { getToken: true }
327
- }
328
- );
312
+ return await $fetch(AUTH_SESSION_PATH, {
313
+ method: "POST",
314
+ body: { getToken: true }
315
+ });
329
316
  } catch {
330
317
  return { hasSession: false, token: null };
331
318
  }
@@ -21,4 +21,4 @@ export declare function useAuthToken(): ComputedRef<string | null>;
21
21
  */
22
22
  export declare function requireConvexAuthMiddleware(options?: {
23
23
  redirectTo?: string;
24
- }): ReturnType<typeof navigateTo> | void;
24
+ }): ReturnType<typeof navigateTo> | undefined;
@@ -1,9 +1,5 @@
1
1
  import { navigateTo } from "nuxt/app";
2
- import {
3
- computed,
4
- ref,
5
- watch
6
- } from "vue";
2
+ import { computed, ref, watch } from "vue";
7
3
  import { useConvexAuth } from "./useConvexAuth.js";
8
4
  import { tryUseConvexContext } from "../utils/context.js";
9
5
  export function useAuthToken() {
@@ -42,11 +38,7 @@ export function useAuthToken() {
42
38
  }
43
39
  };
44
40
  watch(
45
- [
46
- () => auth.isAuthenticated.value,
47
- () => auth.isRefreshing.value,
48
- () => auth.isLoading.value
49
- ],
41
+ [() => auth.isAuthenticated.value, () => auth.isRefreshing.value, () => auth.isLoading.value],
50
42
  () => {
51
43
  void refresh();
52
44
  },
@@ -3,7 +3,7 @@ export function useConvex() {
3
3
  const ctx = useConvexContext();
4
4
  if (!ctx.client) {
5
5
  throw new Error(
6
- "[convex-nuxt] useConvex() is only available in the browser. Use fetchQuery on the server."
6
+ "[use-convex] useConvex() is only available in the browser. Use fetchQuery on the server."
7
7
  );
8
8
  }
9
9
  return ctx.client;
@@ -1,30 +1,18 @@
1
- import { computed, ref, toValue } from "vue";
1
+ import { toValue } from "vue";
2
2
  import { useConvexContext } from "../utils/context.js";
3
+ import { createPendingErrorState } from "../utils/pendingError.js";
3
4
  export function useConvexAction(action) {
4
5
  const ctx = useConvexContext();
5
- const error = ref(null);
6
- const pendingCount = ref(0);
6
+ const { error, pending, withPending } = createPendingErrorState();
7
7
  const run = async (args = {}) => {
8
8
  if (import.meta.server || !ctx.client) {
9
- throw new Error(
10
- "[convex-nuxt] useConvexAction can only run in the browser."
11
- );
12
- }
13
- pendingCount.value++;
14
- error.value = null;
15
- try {
16
- return await ctx.client.action(action, toValue(args));
17
- } catch (cause) {
18
- const err = cause instanceof Error ? cause : new Error(String(cause));
19
- error.value = err;
20
- throw err;
21
- } finally {
22
- pendingCount.value--;
9
+ throw new Error("[use-convex] useConvexAction can only run in the browser.");
23
10
  }
11
+ return await withPending(() => ctx.client.action(action, toValue(args)));
24
12
  };
25
13
  return {
26
14
  run,
27
15
  error,
28
- pending: computed(() => pendingCount.value > 0)
16
+ pending
29
17
  };
30
18
  }
@@ -1,8 +1,4 @@
1
- import {
2
- computed,
3
- toValue,
4
- watch
5
- } from "vue";
1
+ import { computed, toValue, watch } from "vue";
6
2
  import { useHasSsrSessionRef } from "../utils/authCookie.js";
7
3
  import { useConvexContext } from "../utils/context.js";
8
4
  import { resolveConvexAuthState } from "../utils/authState.js";
@@ -10,9 +6,7 @@ export function useConvexAuth(setup) {
10
6
  const ctx = useConvexContext();
11
7
  const auth = ctx.auth;
12
8
  const hasSsrSession = useHasSsrSessionRef();
13
- const showAuthedUi = computed(
14
- () => auth.isAuthenticated.value || hasSsrSession.value
15
- );
9
+ const showAuthedUi = computed(() => auth.isAuthenticated.value || hasSsrSession.value);
16
10
  if (setup) {
17
11
  if (import.meta.server) {
18
12
  return {
@@ -26,7 +20,7 @@ export function useConvexAuth(setup) {
26
20
  const client = ctx.client;
27
21
  if (!client) {
28
22
  throw new Error(
29
- "[convex-nuxt] useConvexAuth({ fetchToken }) requires ConvexClient (browser only)."
23
+ "[use-convex] useConvexAuth({ fetchToken }) requires ConvexClient (browser only)."
30
24
  );
31
25
  }
32
26
  auth.fetchToken = setup.fetchToken;
@@ -67,10 +61,7 @@ export function useConvexAuth(setup) {
67
61
  );
68
62
  };
69
63
  watch(
70
- () => [
71
- toValue(setup.isLoading) ?? false,
72
- toValue(setup.isAuthenticated) ?? true
73
- ],
64
+ () => [toValue(setup.isLoading) ?? false, toValue(setup.isAuthenticated) ?? true],
74
65
  syncFromProvider,
75
66
  { immediate: true }
76
67
  );
@@ -0,0 +1,43 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
2
+ /** Built-in fields the composable always passes into `saveFile`. */
3
+ export type ConvexFileUploadMeta = {
4
+ storageId: string;
5
+ name: string;
6
+ contentType: string;
7
+ size: number;
8
+ };
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'>> {
11
+ /**
12
+ * Mutation that returns a short-lived Convex upload URL
13
+ * (`ctx.storage.generateUploadUrl()`). Must enforce auth itself.
14
+ */
15
+ generateUploadUrl: GenerateUploadUrl;
16
+ /**
17
+ * Mutation that persists `storageId` + file metadata after the POST succeeds.
18
+ * Must accept at least `storageId`, `name`, `contentType`, and `size`.
19
+ */
20
+ saveFile: SaveFile;
21
+ }
22
+ /**
23
+ * POST a file to a Convex-generated upload URL with upload progress.
24
+ * Convex expects `Content-Type` matching the file and returns `{ storageId }`.
25
+ */
26
+ export declare function postFileToUploadUrl(uploadUrl: string, file: File, onProgress?: (fraction: number) => void): Promise<string>;
27
+ /**
28
+ * Browser helper for Convex's three-step file upload flow:
29
+ * generate upload URL → POST file → save `storageId` via mutation.
30
+ *
31
+ * Returns `{ upload, pending, error, progress }`. `progress` is `0..1` while
32
+ * the bytes are in flight, otherwise `null`.
33
+ *
34
+ * Safe to call during SSR setup — work only runs when `upload()` is invoked
35
+ * in the browser. Your Convex mutations must enforce auth; never expose an
36
+ * unauthenticated `generateUploadUrl`.
37
+ */
38
+ export declare function useConvexFileUpload<GenerateUploadUrl extends FunctionReference<'mutation'>, SaveFile extends FunctionReference<'mutation'>>(options: UseConvexFileUploadOptions<GenerateUploadUrl, SaveFile>): {
39
+ upload: (file: File, extra?: ConvexFileUploadExtraArgs<SaveFile> extends Record<string, never> ? undefined : ConvexFileUploadExtraArgs<SaveFile>) => Promise<FunctionReturnType<SaveFile>>;
40
+ error: import("vue").Ref<Error | null, Error | null>;
41
+ pending: import("vue").ComputedRef<boolean>;
42
+ progress: import("vue").ComputedRef<number | null>;
43
+ };