nuxt-convex-module 0.0.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/chunks/index.mjs +64 -0
  4. package/dist/devtools-client/200.html +1 -0
  5. package/dist/devtools-client/404.html +1 -0
  6. package/dist/devtools-client/_nuxt/B29GGJ5n.js +1 -0
  7. package/dist/devtools-client/_nuxt/BSDEpQet.js +1 -0
  8. package/dist/devtools-client/_nuxt/BY23OQOo.js +1 -0
  9. package/dist/devtools-client/_nuxt/BZYRaL-G.js +1 -0
  10. package/dist/devtools-client/_nuxt/Byw6FxnC.js +1 -0
  11. package/dist/devtools-client/_nuxt/CJJ_hcZN.js +1 -0
  12. package/dist/devtools-client/_nuxt/CS6WMFOx.js +2 -0
  13. package/dist/devtools-client/_nuxt/CZbAGcNC.js +1 -0
  14. package/dist/devtools-client/_nuxt/Cr6_aklh.js +2 -0
  15. package/dist/devtools-client/_nuxt/DNfpyBVU.js +3 -0
  16. package/dist/devtools-client/_nuxt/DPWXUmcI.js +1 -0
  17. package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
  18. package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +1 -0
  19. package/dist/devtools-client/_nuxt/dy_xXcv9.js +1 -0
  20. package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +1 -0
  21. package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +1 -0
  22. package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +1 -0
  23. package/dist/devtools-client/_nuxt/ncgEdbEt.js +1 -0
  24. package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +1 -0
  25. package/dist/devtools-client/auth/index.html +1 -0
  26. package/dist/devtools-client/icon.svg +6 -0
  27. package/dist/devtools-client/index.html +1 -0
  28. package/dist/devtools-client/logs/index.html +1 -0
  29. package/dist/devtools-client/queries/index.html +1 -0
  30. package/dist/module.d.ts +189 -0
  31. package/dist/module.js +489 -0
  32. package/dist/module.json +13 -0
  33. package/dist/runtime/auth0/vue/index.d.ts +58 -0
  34. package/dist/runtime/auth0/vue/index.js +33 -0
  35. package/dist/runtime/better-auth/nuxt/middleware.d.ts +14 -0
  36. package/dist/runtime/better-auth/nuxt/middleware.js +39 -0
  37. package/dist/runtime/better-auth/nuxt/proxy.d.ts +6 -0
  38. package/dist/runtime/better-auth/nuxt/proxy.js +11 -0
  39. package/dist/runtime/better-auth/nuxt/server.d.ts +106 -0
  40. package/dist/runtime/better-auth/nuxt/server.js +123 -0
  41. package/dist/runtime/better-auth/vue/auth-boundary.d.ts +131 -0
  42. package/dist/runtime/better-auth/vue/auth-boundary.js +78 -0
  43. package/dist/runtime/better-auth/vue/client.d.ts +358 -0
  44. package/dist/runtime/better-auth/vue/client.js +5 -0
  45. package/dist/runtime/better-auth/vue/cross-domain.d.ts +39 -0
  46. package/dist/runtime/better-auth/vue/cross-domain.js +45 -0
  47. package/dist/runtime/better-auth/vue/hydration.d.ts +20 -0
  48. package/dist/runtime/better-auth/vue/hydration.js +52 -0
  49. package/dist/runtime/better-auth/vue/index.d.ts +12 -0
  50. package/dist/runtime/better-auth/vue/index.js +6 -0
  51. package/dist/runtime/better-auth/vue/plugin.client.d.ts +6 -0
  52. package/dist/runtime/better-auth/vue/plugin.client.js +32 -0
  53. package/dist/runtime/better-auth/vue/plugin.server.d.ts +24 -0
  54. package/dist/runtime/better-auth/vue/plugin.server.js +42 -0
  55. package/dist/runtime/better-auth/vue/use-auth.d.ts +92 -0
  56. package/dist/runtime/better-auth/vue/use-auth.js +82 -0
  57. package/dist/runtime/clerk/vue/index.d.ts +81 -0
  58. package/dist/runtime/clerk/vue/index.js +49 -0
  59. package/dist/runtime/devtools/bridge.d.ts +12 -0
  60. package/dist/runtime/devtools/bridge.js +191 -0
  61. package/dist/runtime/devtools/plugin.client.d.ts +8 -0
  62. package/dist/runtime/devtools/plugin.client.js +34 -0
  63. package/dist/runtime/devtools/types.d.ts +78 -0
  64. package/dist/runtime/devtools/types.js +0 -0
  65. package/dist/runtime/nuxt/composables/use-async-query.d.ts +144 -0
  66. package/dist/runtime/nuxt/composables/use-async-query.js +104 -0
  67. package/dist/runtime/nuxt/config.d.ts +7 -0
  68. package/dist/runtime/nuxt/config.js +12 -0
  69. package/dist/runtime/nuxt/index.d.ts +136 -0
  70. package/dist/runtime/nuxt/index.js +91 -0
  71. package/dist/runtime/polar/vue/components.d.ts +184 -0
  72. package/dist/runtime/polar/vue/components.js +115 -0
  73. package/dist/runtime/vue/auth/helpers.d.ts +67 -0
  74. package/dist/runtime/vue/auth/helpers.js +50 -0
  75. package/dist/runtime/vue/auth/index.d.ts +125 -0
  76. package/dist/runtime/vue/auth/index.js +97 -0
  77. package/dist/runtime/vue/client.d.ts +305 -0
  78. package/dist/runtime/vue/client.js +382 -0
  79. package/dist/runtime/vue/composables/use-action.d.ts +62 -0
  80. package/dist/runtime/vue/composables/use-action.js +13 -0
  81. package/dist/runtime/vue/composables/use-connection-state.d.ts +30 -0
  82. package/dist/runtime/vue/composables/use-connection-state.js +14 -0
  83. package/dist/runtime/vue/composables/use-mutation.d.ts +76 -0
  84. package/dist/runtime/vue/composables/use-mutation.js +34 -0
  85. package/dist/runtime/vue/composables/use-paginated-query.d.ts +132 -0
  86. package/dist/runtime/vue/composables/use-paginated-query.js +513 -0
  87. package/dist/runtime/vue/composables/use-queries.d.ts +62 -0
  88. package/dist/runtime/vue/composables/use-queries.js +37 -0
  89. package/dist/runtime/vue/composables/use-query.d.ts +87 -0
  90. package/dist/runtime/vue/composables/use-query.js +53 -0
  91. package/dist/runtime/vue/composables/use-storage-url.d.ts +46 -0
  92. package/dist/runtime/vue/composables/use-storage-url.js +9 -0
  93. package/dist/runtime/vue/composables/use-subscription.d.ts +15 -0
  94. package/dist/runtime/vue/composables/use-subscription.js +33 -0
  95. package/dist/runtime/vue/composables/use-upload-queue.d.ts +117 -0
  96. package/dist/runtime/vue/composables/use-upload-queue.js +115 -0
  97. package/dist/runtime/vue/composables/use-upload.d.ts +134 -0
  98. package/dist/runtime/vue/composables/use-upload.js +114 -0
  99. package/dist/runtime/vue/hydration.d.ts +66 -0
  100. package/dist/runtime/vue/hydration.js +46 -0
  101. package/dist/runtime/vue/index.d.ts +94 -0
  102. package/dist/runtime/vue/index.js +42 -0
  103. package/dist/runtime/vue/plugin.d.ts +18 -0
  104. package/dist/runtime/vue/plugin.js +16 -0
  105. package/dist/runtime/vue/provide.d.ts +39 -0
  106. package/dist/runtime/vue/provide.js +15 -0
  107. package/dist/runtime/vue/queries-observer.d.ts +41 -0
  108. package/dist/runtime/vue/queries-observer.js +126 -0
  109. package/dist/types.d.ts +3 -0
  110. package/package.json +233 -0
@@ -0,0 +1,53 @@
1
+ import { makeFunctionReference } from "convex/server";
2
+ import { computed, toValue } from "vue";
3
+ import { useConvexQueries } from "./use-queries.js";
4
+ export function useQuery(query, ...args) {
5
+ const queryReference = typeof query === "string" ? makeFunctionReference(query) : query;
6
+ const queries = computed(() => {
7
+ const rawArgs = toValue(args[0]) ?? {};
8
+ const skip = rawArgs === "skip";
9
+ const argsObject = rawArgs === "skip" ? {} : rawArgs;
10
+ return skip ? {} : { query: { query: queryReference, args: argsObject } };
11
+ });
12
+ const results = useConvexQueries(queries);
13
+ return computed(() => {
14
+ const result = results.value["query"];
15
+ if (result instanceof Error) {
16
+ throw result;
17
+ }
18
+ return result;
19
+ });
20
+ }
21
+ export function useQuery_experimental(options) {
22
+ const throwOnError = options.throwOnError ?? false;
23
+ const queryReference = typeof options.query === "string" ? makeFunctionReference(options.query) : options.query;
24
+ const queries = computed(() => {
25
+ const rawArgs = toValue(options.args);
26
+ const skip = rawArgs === "skip";
27
+ const argsObject = !skip ? rawArgs : {};
28
+ return skip ? {} : { query: { query: queryReference, args: argsObject } };
29
+ });
30
+ const results = useConvexQueries(queries);
31
+ return computed(() => {
32
+ const result = results.value["query"];
33
+ if (result instanceof Error) {
34
+ if (throwOnError) {
35
+ throw result;
36
+ }
37
+ return {
38
+ error: result,
39
+ status: "error"
40
+ };
41
+ }
42
+ if (result === void 0) {
43
+ return {
44
+ status: "pending"
45
+ };
46
+ }
47
+ return {
48
+ data: result,
49
+ status: "success"
50
+ };
51
+ });
52
+ }
53
+ export const useConvexQuery = useQuery;
@@ -0,0 +1,46 @@
1
+ import type { FunctionReference } from 'convex/server';
2
+ import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
+ import type { StorageId } from './use-upload.js';
4
+ /**
5
+ * A `getUrl` query: takes `{ storageId }` and returns the file's served URL,
6
+ * or `null` if the file no longer exists (`await ctx.storage.getUrl(id)`).
7
+ *
8
+ * @public
9
+ */
10
+ export type GetStorageUrl = FunctionReference<'query', 'public', {
11
+ storageId: StorageId;
12
+ }, string | null>;
13
+ /**
14
+ * Reactively resolve the served URL for a Convex stored file.
15
+ *
16
+ * Thin wrapper over {@link useQuery} that handles the common ergonomics of
17
+ * serving stored files: it accepts a reactive storage id and automatically
18
+ * skips the query (returning `undefined`) while the id is `null`/`undefined`,
19
+ * so you can bind it straight to an id ref without juggling `'skip'`.
20
+ *
21
+ * @example
22
+ * ```vue
23
+ * <script setup lang="ts">
24
+ * import { useStorageUrl } from '#imports'
25
+ * import { api } from '#convex/api'
26
+ *
27
+ * const { storageId } = useUpload(api.images.generateUploadUrl)
28
+ * const url = useStorageUrl(api.images.getUrl, storageId)
29
+ * </script>
30
+ *
31
+ * <template>
32
+ * <img v-if="url" :src="url">
33
+ * </template>
34
+ * ```
35
+ *
36
+ * @param getUrl - A `FunctionReference` for the public `getUrl` query.
37
+ * @param storageId - The storage id to resolve. Accepts a ref, computed, or
38
+ * getter; `null`/`undefined` skips the query.
39
+ * @returns A computed ref of the served URL: `string | null` once loaded
40
+ * (`null` if the file is gone), or `undefined` while loading or skipped.
41
+ *
42
+ * @public
43
+ */
44
+ export declare function useStorageUrl(getUrl: GetStorageUrl, storageId: MaybeRefOrGetter<StorageId | string | null | undefined>): ComputedRef<string | null | undefined>;
45
+ /** @public */
46
+ export declare const useConvexStorageUrl: typeof useStorageUrl;
@@ -0,0 +1,9 @@
1
+ import { toValue } from "vue";
2
+ import { useQuery } from "./use-query.js";
3
+ export function useStorageUrl(getUrl, storageId) {
4
+ return useQuery(getUrl, () => {
5
+ const id = toValue(storageId);
6
+ return id ? { storageId: id } : "skip";
7
+ });
8
+ }
9
+ export const useConvexStorageUrl = useStorageUrl;
@@ -0,0 +1,15 @@
1
+ import { type ShallowRef } from 'vue';
2
+ /**
3
+ * Composable used for safely managing subscriptions in concurrent mode.
4
+ *
5
+ * In order to avoid removing and re-adding subscriptions each time this composable is called,
6
+ * the parameters passed to this composable should be memoized in some way–
7
+ * either by wrapping the entire params object with useMemo()
8
+ * or by wrapping the individual callbacks with useCallback().
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function useSubscription<Value>({ getCurrentValue, subscribe, }: {
13
+ getCurrentValue: () => Value;
14
+ subscribe: (callback: () => void) => () => void;
15
+ }): ShallowRef<Value>;
@@ -0,0 +1,33 @@
1
+ import { shallowRef, watchEffect } from "vue";
2
+ export function useSubscription({
3
+ // (Synchronously) returns the current value of our subscription.
4
+ getCurrentValue,
5
+ // This function is passed an event handler to attach to the subscription.
6
+ // It should return an unsubscribe function that removes the handler.
7
+ subscribe
8
+ }) {
9
+ const state = shallowRef(getCurrentValue());
10
+ watchEffect((onCleanup) => {
11
+ if (import.meta.server) {
12
+ return;
13
+ }
14
+ let didUnsubscribe = false;
15
+ const checkForUpdates = () => {
16
+ if (didUnsubscribe) {
17
+ return;
18
+ }
19
+ const value = getCurrentValue();
20
+ if (state.value === value) {
21
+ return;
22
+ }
23
+ state.value = value;
24
+ };
25
+ const unsubscribe = subscribe(checkForUpdates);
26
+ checkForUpdates();
27
+ onCleanup(() => {
28
+ didUnsubscribe = true;
29
+ unsubscribe();
30
+ });
31
+ });
32
+ return state;
33
+ }
@@ -0,0 +1,117 @@
1
+ import { type Ref } from 'vue';
2
+ import { type GenerateUploadUrl, type StorageId } from './use-upload.js';
3
+ /**
4
+ * Lifecycle status of a single item in an upload queue.
5
+ *
6
+ * @public
7
+ */
8
+ export type UploadItemStatus = 'pending' | 'uploading' | 'success' | 'error';
9
+ /**
10
+ * A single entry in the queue managed by {@link useUploadQueue}.
11
+ *
12
+ * @public
13
+ */
14
+ export interface UploadQueueItem {
15
+ /** Stable id for this queue entry (not the storage id). */
16
+ id: string;
17
+ /** The file or blob being uploaded. */
18
+ file: Blob;
19
+ /** Current upload status. */
20
+ status: UploadItemStatus;
21
+ /** Upload progress for this item, from `0` to `1`. */
22
+ progress: number;
23
+ /** The storage id once the upload succeeds, or `null`. */
24
+ storageId: StorageId | null;
25
+ /** The error if this item failed, or `null`. */
26
+ error: Error | null;
27
+ }
28
+ /**
29
+ * Options for {@link useUploadQueue}.
30
+ *
31
+ * @public
32
+ */
33
+ export interface UseUploadQueueOptions {
34
+ /** Maximum number of uploads to run at once. Defaults to `3`. */
35
+ concurrency?: number;
36
+ /** Called when an individual item finishes uploading. */
37
+ onItemSuccess?: (storageId: StorageId, item: UploadQueueItem) => void | Promise<void>;
38
+ /** Called when an individual item fails. */
39
+ onItemError?: (error: Error, item: UploadQueueItem) => void;
40
+ /** Called once every queued item has settled (succeeded or failed). */
41
+ onComplete?: (items: UploadQueueItem[]) => void;
42
+ }
43
+ /** Accepted inputs to {@link VueUploadQueue.enqueue}. */
44
+ type EnqueueInput = Blob | Blob[] | FileList | null | undefined;
45
+ /**
46
+ * The reactive queue returned by {@link useUploadQueue}.
47
+ *
48
+ * @public
49
+ */
50
+ export interface VueUploadQueue {
51
+ /** Reactive list of queued items, in insertion order. */
52
+ items: Readonly<Ref<UploadQueueItem[]>>;
53
+ /** Add one or more files to the queue; uploading starts automatically. */
54
+ enqueue: (files: EnqueueInput) => void;
55
+ /** `true` while any item is pending or uploading. */
56
+ isUploading: Readonly<Ref<boolean>>;
57
+ /** Aggregate progress across all items, from `0` to `1`. */
58
+ progress: Readonly<Ref<number>>;
59
+ /** Number of items waiting to start. */
60
+ pendingCount: Readonly<Ref<number>>;
61
+ /** Number of items currently uploading. */
62
+ activeCount: Readonly<Ref<number>>;
63
+ /** Abort all in-flight uploads (queued items stay `pending`). */
64
+ cancel: () => void;
65
+ /** Remove an item by id, aborting it first if it is uploading. */
66
+ remove: (id: string) => void;
67
+ /** Abort everything and empty the queue. */
68
+ clear: () => void;
69
+ }
70
+ /**
71
+ * Upload many files to Convex file storage with bounded concurrency and
72
+ * per-item reactive progress.
73
+ *
74
+ * Built on the same flow as {@link useUpload}, but manages a queue: call
75
+ * {@link VueUploadQueue.enqueue} with one or more files (or a `FileList` from
76
+ * a multi-file `<input>`) and the queue uploads up to `concurrency` at a time,
77
+ * exposing per-item and aggregate progress.
78
+ *
79
+ * @example
80
+ * ```vue
81
+ * <script setup lang="ts">
82
+ * import { useUploadQueue, useMutation } from '#imports'
83
+ * import { api } from '#convex/api'
84
+ *
85
+ * const saveImage = useMutation(api.images.save)
86
+ * const { items, enqueue, progress, isUploading } = useUploadQueue(
87
+ * api.images.generateUploadUrl,
88
+ * { concurrency: 4, onItemSuccess: storageId => saveImage({ storageId }) },
89
+ * )
90
+ *
91
+ * function onPick(event: Event) {
92
+ * enqueue((event.target as HTMLInputElement).files)
93
+ * }
94
+ * </script>
95
+ *
96
+ * <template>
97
+ * <input type="file" multiple @change="onPick">
98
+ * <progress v-if="isUploading" :value="progress" />
99
+ * <ul>
100
+ * <li v-for="item in items" :key="item.id">
101
+ * {{ item.status }} — {{ Math.round(item.progress * 100) }}%
102
+ * </li>
103
+ * </ul>
104
+ * </template>
105
+ * ```
106
+ *
107
+ * @param generateUploadUrl - A `FunctionReference` for the public
108
+ * `generateUploadUrl` mutation.
109
+ * @param options - Optional {@link UseUploadQueueOptions}.
110
+ * @returns A {@link VueUploadQueue}.
111
+ *
112
+ * @public
113
+ */
114
+ export declare function useUploadQueue(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions): VueUploadQueue;
115
+ /** @public */
116
+ export declare const useConvexUploadQueue: typeof useUploadQueue;
117
+ export {};
@@ -0,0 +1,115 @@
1
+ import { computed, ref } from "vue";
2
+ import { useConvexOrThrow } from "../client.js";
3
+ import { uploadFile } from "./use-upload.js";
4
+ function normalizeFiles(input) {
5
+ if (!input) return [];
6
+ if (input instanceof Blob) return [input];
7
+ if (Array.isArray(input)) return input;
8
+ return Array.from(input);
9
+ }
10
+ let queueItemId = 0;
11
+ function nextQueueItemId() {
12
+ return `upload-${++queueItemId}`;
13
+ }
14
+ export function useUploadQueue(generateUploadUrl, options = {}) {
15
+ const convex = useConvexOrThrow("useUploadQueue");
16
+ const concurrency = Math.max(1, Math.floor(options.concurrency ?? 3));
17
+ const items = ref([]);
18
+ const controllers = /* @__PURE__ */ new Map();
19
+ const activeCount = computed(
20
+ () => items.value.filter((item) => item.status === "uploading").length
21
+ );
22
+ const pendingCount = computed(
23
+ () => items.value.filter((item) => item.status === "pending").length
24
+ );
25
+ const isUploading = computed(() => activeCount.value > 0 || pendingCount.value > 0);
26
+ const progress = computed(() => {
27
+ if (items.value.length === 0) return 0;
28
+ const total = items.value.reduce((sum, item) => sum + item.progress, 0);
29
+ return total / items.value.length;
30
+ });
31
+ function pump() {
32
+ if (typeof XMLHttpRequest === "undefined") return;
33
+ let slots = concurrency - activeCount.value;
34
+ for (const item of items.value) {
35
+ if (slots <= 0) break;
36
+ if (item.status !== "pending") continue;
37
+ slots--;
38
+ void run(item);
39
+ }
40
+ }
41
+ async function run(item) {
42
+ item.status = "uploading";
43
+ const controller = new AbortController();
44
+ controllers.set(item.id, controller);
45
+ try {
46
+ const url = await convex.mutation(generateUploadUrl, {});
47
+ const storageId = await uploadFile({
48
+ url,
49
+ file: item.file,
50
+ signal: controller.signal,
51
+ onProgress: (value) => {
52
+ item.progress = value;
53
+ }
54
+ });
55
+ item.storageId = storageId;
56
+ item.progress = 1;
57
+ item.status = "success";
58
+ await options.onItemSuccess?.(storageId, item);
59
+ } catch (caught) {
60
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
61
+ item.error = failure;
62
+ item.status = "error";
63
+ options.onItemError?.(failure, item);
64
+ } finally {
65
+ controllers.delete(item.id);
66
+ if (pendingCount.value === 0 && activeCount.value === 0) {
67
+ options.onComplete?.(items.value);
68
+ } else {
69
+ pump();
70
+ }
71
+ }
72
+ }
73
+ function enqueue(files) {
74
+ const incoming = normalizeFiles(files);
75
+ if (incoming.length === 0) return;
76
+ items.value.push(
77
+ ...incoming.map((file) => ({
78
+ id: nextQueueItemId(),
79
+ file,
80
+ status: "pending",
81
+ progress: 0,
82
+ storageId: null,
83
+ error: null
84
+ }))
85
+ );
86
+ pump();
87
+ }
88
+ function cancel() {
89
+ for (const controller of controllers.values()) {
90
+ controller.abort();
91
+ }
92
+ }
93
+ function remove(id) {
94
+ controllers.get(id)?.abort();
95
+ controllers.delete(id);
96
+ items.value = items.value.filter((item) => item.id !== id);
97
+ }
98
+ function clear() {
99
+ cancel();
100
+ controllers.clear();
101
+ items.value = [];
102
+ }
103
+ return {
104
+ items,
105
+ enqueue,
106
+ isUploading,
107
+ progress,
108
+ pendingCount,
109
+ activeCount,
110
+ cancel,
111
+ remove,
112
+ clear
113
+ };
114
+ }
115
+ export const useConvexUploadQueue = useUploadQueue;
@@ -0,0 +1,134 @@
1
+ import type { FunctionReference } from 'convex/server';
2
+ import type { GenericId } from 'convex/values';
3
+ import { type Ref } from 'vue';
4
+ /**
5
+ * A `generateUploadUrl` mutation: takes no arguments and returns a one-time
6
+ * Convex upload URL string (`await ctx.storage.generateUploadUrl()`).
7
+ *
8
+ * @public
9
+ */
10
+ export type GenerateUploadUrl = FunctionReference<'mutation', 'public', Record<string, never>, string>;
11
+ /**
12
+ * A Convex file storage id (`Id<'_storage'>`), branded so it is directly
13
+ * assignable to mutation/query args validated with `v.id('_storage')`.
14
+ *
15
+ * @public
16
+ */
17
+ export type StorageId = GenericId<'_storage'>;
18
+ /**
19
+ * Options for the low-level {@link uploadFile} helper.
20
+ *
21
+ * @public
22
+ */
23
+ export interface UploadFileOptions {
24
+ /** The one-time Convex upload URL from a `generateUploadUrl` mutation. */
25
+ url: string;
26
+ /** The file or blob to upload. */
27
+ file: Blob;
28
+ /** Receives upload progress as a fraction from `0` to `1`. */
29
+ onProgress?: (progress: number) => void;
30
+ /** Abort signal to cancel the in-flight upload. */
31
+ signal?: AbortSignal;
32
+ }
33
+ /**
34
+ * Upload a single file to a Convex upload URL, resolving to its storage id.
35
+ *
36
+ * Low-level building block used by {@link useUpload} and
37
+ * {@link useUploadQueue}. Uses `XMLHttpRequest` so real upload progress and
38
+ * cancellation are available — `fetch` cannot report request-body progress.
39
+ *
40
+ * Browser-only: throws if no `XMLHttpRequest` global is available (e.g. during
41
+ * SSR).
42
+ *
43
+ * @param options - {@link UploadFileOptions}.
44
+ * @returns A promise of the new {@link StorageId}.
45
+ *
46
+ * @public
47
+ */
48
+ export declare function uploadFile(options: UploadFileOptions): Promise<StorageId>;
49
+ /**
50
+ * Options for {@link useUpload}.
51
+ *
52
+ * @public
53
+ */
54
+ export interface UseUploadOptions {
55
+ /** Called with upload progress as a fraction from `0` to `1`. */
56
+ onProgress?: (progress: number) => void;
57
+ /** Called once the file is stored, with the new storage id and the file. */
58
+ onSuccess?: (storageId: StorageId, file: Blob) => void | Promise<void>;
59
+ /** Called if generating the upload URL or the upload itself fails. */
60
+ onError?: (error: Error, file: Blob) => void;
61
+ }
62
+ /**
63
+ * The reactive uploader returned by {@link useUpload}.
64
+ *
65
+ * @public
66
+ */
67
+ export interface VueUpload {
68
+ /**
69
+ * Upload a file or blob, resolving to its {@link StorageId}.
70
+ *
71
+ * Resolves to `null` (instead of throwing) if the upload fails or is called
72
+ * outside the browser — inspect {@link VueUpload.error} for the reason.
73
+ */
74
+ upload: (file: Blob) => Promise<StorageId | null>;
75
+ /** `true` while an upload is in flight. */
76
+ isUploading: Readonly<Ref<boolean>>;
77
+ /** Upload progress of the current/last upload, from `0` to `1`. */
78
+ progress: Readonly<Ref<number>>;
79
+ /** The error from the most recent failed upload, or `null`. */
80
+ error: Readonly<Ref<Error | null>>;
81
+ /** The storage id from the most recent successful upload, or `null`. */
82
+ storageId: Readonly<Ref<StorageId | null>>;
83
+ /** Abort the in-flight upload, if any. */
84
+ cancel: () => void;
85
+ /** Reset `progress`, `error`, and `storageId` back to their initial values. */
86
+ reset: () => void;
87
+ }
88
+ /**
89
+ * Upload files to Convex file storage from a component, with reactive
90
+ * progress, error, and cancellation state.
91
+ *
92
+ * Wraps the standard Convex flow — call a `generateUploadUrl` mutation, `POST`
93
+ * the file to the returned URL, and read back the storage id — and exposes it
94
+ * as reactive refs. Pass the resulting {@link StorageId} to your own mutation
95
+ * to persist it in your data model.
96
+ *
97
+ * Uploads are client-only: calling `upload()` during SSR resolves to `null`
98
+ * and sets {@link VueUpload.error}.
99
+ *
100
+ * @example
101
+ * ```vue
102
+ * <script setup lang="ts">
103
+ * import { useUpload, useMutation } from '#imports'
104
+ * import { api } from '#convex/api'
105
+ *
106
+ * const saveImage = useMutation(api.images.save)
107
+ * const { upload, isUploading, progress, error } = useUpload(
108
+ * api.images.generateUploadUrl,
109
+ * { onSuccess: storageId => saveImage({ storageId }) },
110
+ * )
111
+ *
112
+ * async function onPick(event: Event) {
113
+ * const file = (event.target as HTMLInputElement).files?.[0]
114
+ * if (file) await upload(file)
115
+ * }
116
+ * </script>
117
+ *
118
+ * <template>
119
+ * <input type="file" :disabled="isUploading" @change="onPick">
120
+ * <progress v-if="isUploading" :value="progress" />
121
+ * <p v-if="error">{{ error.message }}</p>
122
+ * </template>
123
+ * ```
124
+ *
125
+ * @param generateUploadUrl - A `FunctionReference` for the public
126
+ * `generateUploadUrl` mutation.
127
+ * @param options - Optional {@link UseUploadOptions} callbacks.
128
+ * @returns A {@link VueUpload}.
129
+ *
130
+ * @public
131
+ */
132
+ export declare function useUpload(generateUploadUrl: GenerateUploadUrl, options?: UseUploadOptions): VueUpload;
133
+ /** @public */
134
+ export declare const useConvexUpload: typeof useUpload;
@@ -0,0 +1,114 @@
1
+ import { ref, shallowRef } from "vue";
2
+ import { useConvexOrThrow } from "../client.js";
3
+ export function uploadFile(options) {
4
+ const { url, file, onProgress, signal } = options;
5
+ if (typeof XMLHttpRequest === "undefined") {
6
+ return Promise.reject(
7
+ new Error("uploadFile can only run in the browser (no XMLHttpRequest available).")
8
+ );
9
+ }
10
+ return new Promise((resolve, reject) => {
11
+ if (signal?.aborted) {
12
+ reject(new DOMException("Upload aborted.", "AbortError"));
13
+ return;
14
+ }
15
+ const xhr = new XMLHttpRequest();
16
+ xhr.open("POST", url, true);
17
+ if (file.type) {
18
+ xhr.setRequestHeader("Content-Type", file.type);
19
+ }
20
+ const onAbort = () => xhr.abort();
21
+ signal?.addEventListener("abort", onAbort, { once: true });
22
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
23
+ xhr.upload.onprogress = (event) => {
24
+ if (event.lengthComputable && event.total > 0) {
25
+ onProgress?.(event.loaded / event.total);
26
+ }
27
+ };
28
+ xhr.onload = () => {
29
+ cleanup();
30
+ if (xhr.status >= 200 && xhr.status < 300) {
31
+ try {
32
+ const { storageId } = JSON.parse(xhr.responseText);
33
+ onProgress?.(1);
34
+ resolve(storageId);
35
+ } catch {
36
+ reject(new Error(`Convex upload returned an unexpected response: ${xhr.responseText}`));
37
+ }
38
+ } else {
39
+ reject(new Error(`Convex upload failed with status ${xhr.status}: ${xhr.responseText}`));
40
+ }
41
+ };
42
+ xhr.onerror = () => {
43
+ cleanup();
44
+ reject(new Error("Network error during Convex file upload."));
45
+ };
46
+ xhr.onabort = () => {
47
+ cleanup();
48
+ reject(new DOMException("Upload aborted.", "AbortError"));
49
+ };
50
+ xhr.send(file);
51
+ });
52
+ }
53
+ export function useUpload(generateUploadUrl, options = {}) {
54
+ const convex = useConvexOrThrow("useUpload");
55
+ const isUploading = ref(false);
56
+ const progress = ref(0);
57
+ const error = shallowRef(null);
58
+ const storageId = shallowRef(null);
59
+ let controller = null;
60
+ async function upload(file) {
61
+ if (typeof XMLHttpRequest === "undefined") {
62
+ const browserOnly = new Error("useUpload().upload() can only run in the browser.");
63
+ error.value = browserOnly;
64
+ options.onError?.(browserOnly, file);
65
+ return null;
66
+ }
67
+ controller = new AbortController();
68
+ isUploading.value = true;
69
+ progress.value = 0;
70
+ error.value = null;
71
+ try {
72
+ const url = await convex.mutation(generateUploadUrl, {});
73
+ const id = await uploadFile({
74
+ url,
75
+ file,
76
+ signal: controller.signal,
77
+ onProgress: (value) => {
78
+ progress.value = value;
79
+ options.onProgress?.(value);
80
+ }
81
+ });
82
+ storageId.value = id;
83
+ progress.value = 1;
84
+ await options.onSuccess?.(id, file);
85
+ return id;
86
+ } catch (caught) {
87
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
88
+ error.value = failure;
89
+ options.onError?.(failure, file);
90
+ return null;
91
+ } finally {
92
+ isUploading.value = false;
93
+ controller = null;
94
+ }
95
+ }
96
+ function cancel() {
97
+ controller?.abort();
98
+ }
99
+ function reset() {
100
+ progress.value = 0;
101
+ error.value = null;
102
+ storageId.value = null;
103
+ }
104
+ return {
105
+ upload,
106
+ isUploading,
107
+ progress,
108
+ error,
109
+ storageId,
110
+ cancel,
111
+ reset
112
+ };
113
+ }
114
+ export const useConvexUpload = useUpload;