nuxt-convex 0.0.1-alpha.0 → 0.0.2

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">nuxt-convex</h1>
6
6
 
7
- <p align="center">Nuxt module for <a href="https://convex.dev">Convex</a> - reactive backend with real-time sync, file storage, and auto-imports.</p>
7
+ <p align="center">Nuxt module for <a href="https://convex.dev">Convex</a></p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://npmjs.com/package/nuxt-convex"><img src="https://img.shields.io/npm/v/nuxt-convex/latest.svg?style=flat&colorA=020420&colorB=00DC82" alt="npm version"></a>
@@ -13,495 +13,12 @@
13
13
  <a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js" alt="Nuxt"></a>
14
14
  </p>
15
15
 
16
- > **[Live Demo](https://nuxt-convex-playground.workers.dev)** · [Convex Docs](https://docs.convex.dev)
16
+ > [!WARNING]
17
+ > This library is a work in progress and not ready for production use.
17
18
 
18
- ## Features
19
+ ## Documentation
19
20
 
20
- - **Real-time by default** - Queries auto-update when data changes
21
- - **Virtual modules** - Import from `#convex`, `#convex/api`, and `#convex/storage`
22
- - **Auto-imports** - Composables available without manual imports
23
- - **File storage** - Upload API with auto-scaffolded Convex functions
24
- - **DevTools** - Convex dashboard tab in Nuxt DevTools
25
-
26
- ## Requirements
27
-
28
- - Nuxt 3.0.0 or higher
29
- - A Convex project (run `npx convex dev` to create one)
30
-
31
- ## Setup
32
-
33
- Install the module and its peer dependencies:
34
-
35
- ```bash
36
- pnpm add nuxt-convex convex @convex-vue/core
37
- ```
38
-
39
- Add the module to your Nuxt configuration:
40
-
41
- ```ts [nuxt.config.ts]
42
- export default defineNuxtConfig({
43
- modules: ['nuxt-convex'],
44
- convex: {
45
- storage: true, // Enable file storage (optional)
46
- },
47
- })
48
- ```
49
-
50
- The module reads `CONVEX_URL` or `NUXT_PUBLIC_CONVEX_URL` environment variables automatically. Run `npx convex dev` to start the Convex development server.
51
-
52
- ## Creating Your Backend
53
-
54
- Convex functions live in the `convex/` directory at your project root. See [Convex docs](https://docs.convex.dev/functions) for full reference.
55
-
56
- ### Schema
57
-
58
- Define your database tables with [schemas](https://docs.convex.dev/database/schemas). Tables are created automatically when you push to Convex.
59
-
60
- ```ts [convex/schema.ts]
61
- import { defineSchema, defineTable } from 'convex/server'
62
- import { v } from 'convex/values'
63
-
64
- export default defineSchema({
65
- tasks: defineTable({
66
- title: v.string(),
67
- userId: v.optional(v.string()),
68
- isCompleted: v.boolean(),
69
- createdAt: v.number(),
70
- }).index('by_user', ['userId']),
71
- })
72
- ```
73
-
74
- The `v` object provides [validators](https://docs.convex.dev/database/schemas#validators) for all Convex types: `v.string()`, `v.number()`, `v.boolean()`, `v.id('tableName')`, `v.array()`, `v.object()`, `v.optional()`, `v.union()`, etc.
75
-
76
- ### Queries
77
-
78
- [Queries](https://docs.convex.dev/functions/query-functions) read data from the database. They are reactive - the UI updates automatically when underlying data changes.
79
-
80
- ```ts [convex/tasks.ts]
81
- import { v } from 'convex/values'
82
- import { query } from './_generated/server'
83
-
84
- export const list = query({
85
- args: { userId: v.string() },
86
- handler: async (ctx, { userId }) => {
87
- return await ctx.db
88
- .query('tasks')
89
- .withIndex('by_user', q => q.eq('userId', userId))
90
- .order('desc')
91
- .collect()
92
- },
93
- })
94
-
95
- export const get = query({
96
- args: { id: v.id('tasks') },
97
- handler: async (ctx, { id }) => {
98
- return await ctx.db.get(id)
99
- },
100
- })
101
- ```
102
-
103
- ### Mutations
104
-
105
- [Mutations](https://docs.convex.dev/functions/mutation-functions) write data to the database. They are transactional and run on the server.
106
-
107
- ```ts [convex/tasks.ts]
108
- import { v } from 'convex/values'
109
- import { mutation } from './_generated/server'
110
-
111
- export const add = mutation({
112
- args: { title: v.string(), userId: v.string() },
113
- handler: async (ctx, { title, userId }) => {
114
- return await ctx.db.insert('tasks', {
115
- title,
116
- userId,
117
- isCompleted: false,
118
- createdAt: Date.now(),
119
- })
120
- },
121
- })
122
-
123
- export const toggle = mutation({
124
- args: { id: v.id('tasks') },
125
- handler: async (ctx, { id }) => {
126
- const task = await ctx.db.get(id)
127
- if (task) {
128
- await ctx.db.patch(id, { isCompleted: !task.isCompleted })
129
- }
130
- },
131
- })
132
-
133
- export const remove = mutation({
134
- args: { id: v.id('tasks') },
135
- handler: async (ctx, { id }) => {
136
- await ctx.db.delete(id)
137
- },
138
- })
139
- ```
140
-
141
- ### Actions
142
-
143
- [Actions](https://docs.convex.dev/functions/actions) run arbitrary code including external API calls, but cannot directly access the database. Use them for third-party integrations.
144
-
145
- ```ts [convex/ai.ts]
146
- import { v } from 'convex/values'
147
- import { action } from './_generated/server'
148
-
149
- export const summarize = action({
150
- args: { text: v.string() },
151
- handler: async (ctx, { text }) => {
152
- const response = await fetch('https://api.openai.com/v1/...', {
153
- method: 'POST',
154
- headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
155
- body: JSON.stringify({ prompt: text }),
156
- })
157
- return await response.json()
158
- },
159
- })
160
- ```
161
-
162
- Actions can call mutations/queries via `ctx.runMutation()` and `ctx.runQuery()`.
163
-
164
- ## Usage
165
-
166
- Composables are auto-imported. They wrap [@convex-vue/core](https://github.com/niconiahi/convex-vue).
167
-
168
- ### useConvexQuery
169
-
170
- Subscribe to a [query](https://docs.convex.dev/functions/query-functions). Returns reactive data that auto-updates when the database changes.
171
-
172
- ```vue [app/pages/tasks.vue]
173
- <script setup lang="ts">
174
- import { api } from '#convex/api'
175
-
176
- const { data: tasks, isLoading, error } = useConvexQuery(api.tasks.list, { userId: 'user_123' })
177
- </script>
178
-
179
- <template>
180
- <div v-if="isLoading">
181
- Loading...
182
- </div>
183
- <div v-else-if="error">
184
- Error: {{ error.message }}
185
- </div>
186
- <ul v-else>
187
- <li v-for="task in tasks" :key="task._id">
188
- {{ task.title }}
189
- </li>
190
- </ul>
191
- </template>
192
- ```
193
-
194
- **Return values:**
195
-
196
- | Property | Type | Description |
197
- | ----------- | --------------------- | ----------------------- |
198
- | `data` | `Ref<T \| undefined>` | Query result, reactive |
199
- | `isLoading` | `Ref<boolean>` | True while initial load |
200
- | `error` | `Ref<Error \| null>` | Error if query failed |
201
-
202
- ### useConvexMutation
203
-
204
- Call a [mutation](https://docs.convex.dev/functions/mutation-functions) to write data.
205
-
206
- ```vue [app/components/AddTask.vue]
207
- <script setup lang="ts">
208
- import { api } from '#convex/api'
209
-
210
- const { mutate: addTask, isLoading, error } = useConvexMutation(api.tasks.add)
211
-
212
- const title = ref('')
213
-
214
- async function handleSubmit() {
215
- await addTask({ title: title.value, userId: 'user_123' })
216
- title.value = ''
217
- }
218
- </script>
219
-
220
- <template>
221
- <form @submit.prevent="handleSubmit">
222
- <input v-model="title" :disabled="isLoading" placeholder="New task...">
223
- <button :disabled="isLoading">
224
- Add
225
- </button>
226
- <p v-if="error">
227
- {{ error.message }}
228
- </p>
229
- </form>
230
- </template>
231
- ```
232
-
233
- **Return values:**
234
-
235
- | Property | Type | Description |
236
- | ----------- | ---------------------- | ------------------------ |
237
- | `mutate` | `(args) => Promise<T>` | Call the mutation |
238
- | `isLoading` | `Ref<boolean>` | True while running |
239
- | `error` | `Ref<Error \| null>` | Error if mutation failed |
240
-
241
- ### useConvexAction
242
-
243
- Call an [action](https://docs.convex.dev/functions/actions) for external API calls or long-running tasks.
244
-
245
- ```vue [app/components/Summarize.vue]
246
- <script setup lang="ts">
247
- import { api } from '#convex/api'
248
-
249
- const { execute: summarize, isLoading, error } = useConvexAction(api.ai.summarize)
250
-
251
- const result = ref('')
252
-
253
- async function handleSummarize(text: string) {
254
- result.value = await summarize({ text })
255
- }
256
- </script>
257
- ```
258
-
259
- **Return values:**
260
-
261
- | Property | Type | Description |
262
- | ----------- | ---------------------- | ---------------------- |
263
- | `execute` | `(args) => Promise<T>` | Call the action |
264
- | `isLoading` | `Ref<boolean>` | True while running |
265
- | `error` | `Ref<Error \| null>` | Error if action failed |
266
-
267
- ### useConvex
268
-
269
- Get the raw [ConvexClient](https://docs.convex.dev/api/classes/browser.ConvexClient) for advanced usage.
270
-
271
- ```vue [app/components/Advanced.vue]
272
- <script setup lang="ts">
273
- import { api } from '#convex/api'
274
-
275
- const client = useConvex()
276
-
277
- // Direct client access for advanced patterns
278
- const result = await client.query(api.tasks.get, { id: 'abc123' })
279
- </script>
280
- ```
281
-
282
- ## File Storage
283
-
284
- Convex provides built-in [file storage](https://docs.convex.dev/file-storage). Enable it in your config to auto-scaffold the required functions.
285
-
286
- ### Setup
287
-
288
- ```ts [nuxt.config.ts]
289
- export default defineNuxtConfig({
290
- modules: ['nuxt-convex'],
291
- convex: {
292
- storage: true,
293
- },
294
- })
295
- ```
296
-
297
- This creates `convex/_hub/storage.ts` with `generateUploadUrl`, `getUrl`, and `remove` functions. Customize as needed.
298
-
299
- Add the `uploads` table to your schema:
300
-
301
- ```ts [convex/schema.ts]
302
- import { defineSchema, defineTable } from 'convex/server'
303
- import { v } from 'convex/values'
304
-
305
- export default defineSchema({
306
- uploads: defineTable({
307
- storageId: v.id('_storage'),
308
- name: v.string(),
309
- type: v.string(),
310
- url: v.optional(v.string()),
311
- userId: v.string(),
312
- createdAt: v.number(),
313
- }).index('by_user', ['userId']),
314
- })
315
- ```
316
-
317
- ### useConvexStorage
318
-
319
- Low-level composable for storage operations. Auto-imported when storage is enabled.
320
-
321
- ```vue [app/components/Storage.vue]
322
- <script setup lang="ts">
323
- import { api } from '#convex/api'
324
-
325
- const { generateUploadUrl, getUrl, remove } = useConvexStorage(api)
326
-
327
- // Get a reactive URL for a stored file
328
- const fileUrl = getUrl('storage_id_here')
329
-
330
- // Generate upload URL for direct upload
331
- const uploadUrl = await generateUploadUrl.mutate()
332
-
333
- // Delete a file
334
- await remove.mutate({ storageId: 'storage_id_here' })
335
- </script>
336
- ```
337
-
338
- **Return values:**
339
-
340
- | Property | Type | Description |
341
- | ------------------- | -------------------------------------------- | --------------------- |
342
- | `generateUploadUrl` | `{ mutate: () => Promise<string> }` | Get URL for uploading |
343
- | `getUrl` | `(storageId: string) => Ref<string \| null>` | Reactive file URL |
344
- | `remove` | `{ mutate: (args) => Promise<void> }` | Delete a file |
345
-
346
- ### useConvexUpload
347
-
348
- High-level composable for file uploads with progress tracking. Auto-imported.
349
-
350
- ```vue [app/pages/upload.vue]
351
- <script setup lang="ts">
352
- import { api } from '#convex/api'
353
-
354
- const { generateUploadUrl } = useConvexStorage(api)
355
- const saveFile = useConvexMutation(api._hub.storage.saveFile)
356
-
357
- const { upload, isUploading, progress, error } = useConvexUpload({
358
- generateUploadUrl,
359
- onSuccess: async (storageId, file) => {
360
- await saveFile.mutate({
361
- storageId,
362
- name: file.name,
363
- type: file.type,
364
- userId: 'user_123',
365
- })
366
- },
367
- onError: err => console.error('Upload failed:', err),
368
- })
369
-
370
- async function handleFileChange(event: Event) {
371
- const file = (event.target as HTMLInputElement).files?.[0]
372
- if (file) {
373
- const storageId = await upload(file)
374
- console.log('Uploaded:', storageId)
375
- }
376
- }
377
- </script>
378
-
379
- <template>
380
- <div>
381
- <input type="file" :disabled="isUploading" @change="handleFileChange">
382
- <p v-if="isUploading">
383
- Uploading... {{ progress }}%
384
- </p>
385
- <p v-if="error">
386
- {{ error.message }}
387
- </p>
388
- </div>
389
- </template>
390
- ```
391
-
392
- **Options:**
393
-
394
- | Option | Type | Description |
395
- | ------------------- | ----------------------------------------- | --------------------------------- |
396
- | `generateUploadUrl` | `{ mutate: () => Promise<string> }` | Required. From `useConvexStorage` |
397
- | `onSuccess` | `(storageId: string, file: File) => void` | Called after successful upload |
398
- | `onError` | `(error: Error) => void` | Called on upload error |
399
-
400
- **Return values:**
401
-
402
- | Property | Type | Description |
403
- | ------------- | ----------------------------------------- | -------------------------------- |
404
- | `upload` | `(file: File) => Promise<string \| null>` | Upload a file, returns storageId |
405
- | `isUploading` | `Ref<boolean>` | True while uploading |
406
- | `progress` | `Ref<number>` | Upload progress 0-100 |
407
- | `error` | `Ref<Error \| null>` | Error if upload failed |
408
-
409
- ## Configuration
410
-
411
- | Option | Type | Default | Description |
412
- | --------- | --------- | ------------------------ | ------------------------------- |
413
- | `url` | `string` | `process.env.CONVEX_URL` | Convex deployment URL |
414
- | `storage` | `boolean` | `false` | Enable file storage integration |
415
-
416
- **Environment variables:**
417
-
418
- | Variable | Description |
419
- | ------------------------ | ----------------------------------------------- |
420
- | `CONVEX_URL` | Convex deployment URL (set by `npx convex dev`) |
421
- | `NUXT_PUBLIC_CONVEX_URL` | Alternative, follows Nuxt convention |
422
-
423
- ## DevTools
424
-
425
- When running in development, the module adds a **Convex** tab to Nuxt DevTools with an embedded Convex dashboard. Access your data, run functions, and view logs directly from DevTools.
426
-
427
- ## Better Auth Integration
428
-
429
- Use [nuxt-better-auth](https://nuxt-better-auth.onmax.me/) for authentication alongside Convex for your app data.
430
-
431
- ### 1. Install dependencies
432
-
433
- ```bash
434
- pnpm add @onmax/nuxt-better-auth
435
- ```
436
-
437
- ### 2. Configure nuxt-better-auth
438
-
439
- ```ts [nuxt.config.ts]
440
- export default defineNuxtConfig({
441
- modules: ['nuxt-convex', '@onmax/nuxt-better-auth'],
442
- convex: { storage: true },
443
- })
444
- ```
445
-
446
- ### 3. Create server auth config
447
-
448
- ```ts [server/auth.config.ts]
449
- import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
450
-
451
- export default defineServerAuth(() => ({
452
- emailAndPassword: { enabled: true },
453
- }))
454
- ```
455
-
456
- See [nuxt-better-auth docs](https://nuxt-better-auth.onmax.me/) for database adapters, social providers, and plugins.
457
-
458
- ### Using Convex as Auth Database (Advanced)
459
-
460
- To store auth data directly in Convex instead of a SQL database:
461
-
462
- ```bash
463
- pnpm add @convex-dev/better-auth better-auth --save-exact
464
- ```
465
-
466
- ```ts [convex/convex.config.ts]
467
- import betterAuth from '@convex-dev/better-auth/convex.config'
468
- import { defineApp } from 'convex/server'
469
-
470
- const app = defineApp()
471
- app.use(betterAuth)
472
- export default app
473
- ```
474
-
475
- ```ts [server/auth.config.ts]
476
- import { components } from '#convex/api'
477
- import { createClient } from '@convex-dev/better-auth'
478
- import { convex } from '@convex-dev/better-auth/server/plugins'
479
- import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
480
-
481
- const authComponent = createClient(components.betterAuth)
482
-
483
- export default defineServerAuth(() => ({
484
- database: authComponent.adapter(),
485
- plugins: [convex()],
486
- emailAndPassword: { enabled: true },
487
- }))
488
- ```
489
-
490
- This stores users, sessions, and accounts in Convex. See [@convex-dev/better-auth](https://github.com/get-convex/convex-better-auth) for details.
491
-
492
- ## Development
493
-
494
- ```bash
495
- pnpm install
496
- pnpm dev:prepare
497
- pnpm dev
498
- ```
499
-
500
- Run tests:
501
-
502
- ```bash
503
- pnpm test
504
- ```
21
+ **[nuxt-convex.onmax.me](https://nuxt-convex.onmax.me/)**
505
22
 
506
23
  ## License
507
24
 
package/dist/module.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
1
+ import * as nuxt_schema from 'nuxt/schema';
2
2
 
3
3
  interface ConvexConfig {
4
4
  /** Convex deployment URL. Auto-detected from CONVEX_URL or NUXT_PUBLIC_CONVEX_URL env vars */
@@ -11,7 +11,7 @@ interface ResolvedConvexConfig {
11
11
  storage: boolean;
12
12
  }
13
13
 
14
- declare const _default: _nuxt_schema.NuxtModule<ConvexConfig, ConvexConfig, false>;
14
+ declare const _default: nuxt_schema.NuxtModule<ConvexConfig, ConvexConfig, false>;
15
15
 
16
16
  export { _default as default };
17
17
  export type { ConvexConfig, ResolvedConvexConfig };
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-convex",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.2",
4
4
  "configKey": "convex",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.0.0"
package/dist/module.mjs CHANGED
@@ -6,7 +6,7 @@ import { consola } from 'consola';
6
6
  import { defu } from 'defu';
7
7
  import { join } from 'pathe';
8
8
 
9
- const version = "0.0.1-alpha.0";
9
+ const version = "0.0.2";
10
10
 
11
11
  const log = consola.withTag("nuxt:convex");
12
12
  const module$1 = defineNuxtModule({
@@ -33,9 +33,11 @@ const module$1 = defineNuxtModule({
33
33
  { url: config.url }
34
34
  );
35
35
  addPlugin({ src: resolve("./runtime/plugin.client"), mode: "client" });
36
- addImports({ name: "useConvexUpload", from: resolve("./runtime/composables/useConvexUpload") });
36
+ addImports([
37
+ { name: "useConvexUpload", from: resolve("./runtime/composables/useConvexUpload") }
38
+ ]);
37
39
  setupConvexApiAlias(nuxt);
38
- setupConvexClient(nuxt);
40
+ setupConvexClient(nuxt, resolve);
39
41
  if (config.storage) {
40
42
  await setupConvexStorage(nuxt, resolve);
41
43
  }
@@ -55,13 +57,14 @@ function resolveConfig(options) {
55
57
  const url = options.url || process.env.CONVEX_URL || process.env.NUXT_PUBLIC_CONVEX_URL || "";
56
58
  return defu(options, { url, storage: false });
57
59
  }
58
- function setupConvexClient(nuxt, _resolve) {
60
+ function setupConvexClient(nuxt, resolve) {
59
61
  const template = addTemplate({
60
62
  filename: "convex/client.mjs",
61
63
  getContents: () => `// Auto-generated by nuxt-convex
62
64
  import { inject } from 'vue'
63
65
  import { CONVEX_INJECTION_KEY } from '@convex-vue/core'
64
- export { useConvexQuery, useConvexMutation, useConvexAction } from '@convex-vue/core'
66
+ export { useConvexMutation, useConvexAction } from '@convex-vue/core'
67
+ export { useConvexQuery } from '${resolve("./runtime/composables/useConvexQuery")}'
65
68
 
66
69
  // Get the Convex client - MUST be called during component setup
67
70
  export function useConvex() {
@@ -76,8 +79,23 @@ export function useConvex() {
76
79
  filename: "types/convex.d.ts",
77
80
  getContents: () => `// Auto-generated by nuxt-convex
78
81
  import type { ConvexClient } from 'convex/browser'
82
+ import type { FunctionReference, FunctionArgs, FunctionReturnType } from 'convex/server'
83
+ import type { AsyncData, AsyncDataOptions } from 'nuxt/app'
84
+ import type { MaybeRefOrGetter } from 'vue'
85
+
79
86
  declare module '#convex' {
80
- export { useConvexQuery, useConvexMutation, useConvexAction } from '@convex-vue/core'
87
+ export { useConvexMutation, useConvexAction } from '@convex-vue/core'
88
+
89
+ export interface UseConvexQueryOptions<T> extends Omit<AsyncDataOptions<T>, 'transform' | 'default'> {
90
+ ssr?: boolean
91
+ }
92
+
93
+ export function useConvexQuery<Query extends FunctionReference<'query'>>(
94
+ query: Query,
95
+ args: MaybeRefOrGetter<FunctionArgs<Query>>,
96
+ options?: UseConvexQueryOptions<FunctionReturnType<Query>>
97
+ ): Promise<AsyncData<FunctionReturnType<Query> | null, Error | null>>
98
+
81
99
  export function useConvex(): ConvexClient
82
100
  }
83
101
  `
@@ -0,0 +1,30 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
2
+ import type { AsyncData, AsyncDataOptions } from 'nuxt/app';
3
+ import type { MaybeRefOrGetter } from 'vue';
4
+ type QueryReference = FunctionReference<'query'>;
5
+ export interface UseConvexQueryOptions<T> extends Omit<AsyncDataOptions<T>, 'transform' | 'default'> {
6
+ /**
7
+ * Enable server-side data fetching.
8
+ * @default true
9
+ */
10
+ ssr?: boolean;
11
+ }
12
+ /**
13
+ * Convex query composable with SSR support.
14
+ *
15
+ * By default, fetches data on the server (SSR) and subscribes to real-time updates on the client.
16
+ * Set `ssr: false` to disable server-side fetching and use client-only mode.
17
+ *
18
+ * @example
19
+ * ```vue
20
+ * <script setup>
21
+ * // SSR + real-time (default)
22
+ * const { data, pending, error } = await useConvexQuery(api.tasks.list, {})
23
+ *
24
+ * // Client-only mode
25
+ * const { data } = await useConvexQuery(api.tasks.list, {}, { ssr: false })
26
+ * </script>
27
+ * ```
28
+ */
29
+ export declare function useConvexQuery<Query extends QueryReference>(query: Query, args: MaybeRefOrGetter<FunctionArgs<Query>>, options?: UseConvexQueryOptions<FunctionReturnType<Query>>): Promise<AsyncData<FunctionReturnType<Query> | null, Error | null>>;
30
+ export {};
@@ -0,0 +1,119 @@
1
+ import { useAsyncData, useNuxtApp, useRuntimeConfig } from "#imports";
2
+ import { CONVEX_INJECTION_KEY } from "@convex-vue/core";
3
+ import { inject, onScopeDispose, ref, toValue, watch } from "vue";
4
+ export async function useConvexQuery(query, args, options = {}) {
5
+ const nuxtApp = useNuxtApp();
6
+ const config = useRuntimeConfig();
7
+ const convexUrl = config.public.convex?.url;
8
+ const client = import.meta.client ? inject(CONVEX_INJECTION_KEY) : null;
9
+ if (!convexUrl) {
10
+ console.warn("[nuxt-convex] No Convex URL configured");
11
+ return createErrorAsyncData(new Error("Convex URL not configured"));
12
+ }
13
+ const ssrEnabled = options.ssr ?? true;
14
+ const shouldUseSSR = ssrEnabled && (import.meta.server || nuxtApp.isHydrating);
15
+ if (shouldUseSSR) {
16
+ return useConvexQuerySSR(query, args, options, convexUrl, client);
17
+ }
18
+ return useConvexQueryClient(query, args, client);
19
+ }
20
+ async function useConvexQuerySSR(query, args, options, convexUrl, client) {
21
+ const queryName = query._name ?? "unknown";
22
+ const sortedArgs = sortObjectKeys(toValue(args));
23
+ const key = `convex:${queryName}:${JSON.stringify(sortedArgs)}`;
24
+ const asyncData = await useAsyncData(
25
+ key,
26
+ async () => {
27
+ const { fetchQuery } = await import("convex/nextjs");
28
+ return fetchQuery(query, toValue(args), { url: convexUrl });
29
+ },
30
+ { ...options, server: options.ssr ?? true }
31
+ );
32
+ if (import.meta.client) {
33
+ setupClientSubscription(query, args, {
34
+ data: asyncData.data,
35
+ error: asyncData.error,
36
+ pending: asyncData.pending,
37
+ status: asyncData.status
38
+ }, client);
39
+ }
40
+ return asyncData;
41
+ }
42
+ function useConvexQueryClient(query, args, client) {
43
+ const data = ref(null);
44
+ const error = ref(null);
45
+ const pending = ref(true);
46
+ const status = ref("pending");
47
+ const state = { data, error, pending, status };
48
+ const { refresh } = setupClientSubscription(query, args, state, client);
49
+ return {
50
+ data,
51
+ pending,
52
+ error,
53
+ status,
54
+ refresh,
55
+ execute: refresh,
56
+ clear: () => {
57
+ data.value = null;
58
+ error.value = null;
59
+ pending.value = false;
60
+ status.value = "idle";
61
+ }
62
+ };
63
+ }
64
+ function setupClientSubscription(query, args, state, client) {
65
+ if (!client) {
66
+ console.warn("[nuxt-convex] Convex client not found. Real-time updates disabled.");
67
+ state.pending.value = false;
68
+ state.status.value = "error";
69
+ state.error.value = new Error("Convex client not found");
70
+ return { refresh: async () => {
71
+ } };
72
+ }
73
+ let unsubscribe = null;
74
+ const subscribe = () => {
75
+ unsubscribe?.();
76
+ const currentArgs = toValue(args);
77
+ unsubscribe = client.onUpdate(query, currentArgs, (result) => {
78
+ if (result !== void 0) {
79
+ state.data.value = result;
80
+ state.pending.value = false;
81
+ state.status.value = "success";
82
+ }
83
+ });
84
+ };
85
+ subscribe();
86
+ watch(() => toValue(args), subscribe, { deep: true });
87
+ onScopeDispose(() => unsubscribe?.());
88
+ return {
89
+ refresh: async () => {
90
+ state.pending.value = true;
91
+ subscribe();
92
+ }
93
+ };
94
+ }
95
+ function createErrorAsyncData(err) {
96
+ return {
97
+ data: ref(null),
98
+ pending: ref(false),
99
+ error: ref(err),
100
+ status: ref("error"),
101
+ refresh: async () => {
102
+ },
103
+ execute: async () => {
104
+ },
105
+ clear: () => {
106
+ }
107
+ };
108
+ }
109
+ function sortObjectKeys(obj) {
110
+ if (obj === null || typeof obj !== "object")
111
+ return obj;
112
+ if (Array.isArray(obj))
113
+ return obj.map(sortObjectKeys);
114
+ const sorted = {};
115
+ for (const key of Object.keys(obj).sort()) {
116
+ sorted[key] = sortObjectKeys(obj[key]);
117
+ }
118
+ return sorted;
119
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-convex",
3
3
  "type": "module",
4
- "version": "0.0.1-alpha.0",
4
+ "version": "0.0.2",
5
5
  "packageManager": "pnpm@10.15.1",
6
6
  "description": "Nuxt module for Convex - reactive backend with real-time sync, file storage, and auto-imports",
7
7
  "author": "onmax",
@@ -72,11 +72,12 @@
72
72
  "@nuxt/module-builder": "catalog:build",
73
73
  "@nuxt/test-utils": "catalog:test",
74
74
  "@types/node": "catalog:types",
75
- "bumpp": "^10.3.2",
75
+ "@vue/test-utils": "catalog:test",
76
+ "bumpp": "catalog:",
76
77
  "convex": "catalog:convex",
77
78
  "eslint": "catalog:lint",
78
79
  "eslint-plugin-format": "catalog:lint",
79
- "nuxt": "catalog:nuxt",
80
+ "nuxt": "catalog:",
80
81
  "typescript": "catalog:build",
81
82
  "vitest": "catalog:test",
82
83
  "vitest-package-exports": "catalog:test",