nuxt-convex-module 0.14.0 → 0.14.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/dist/devtools-client/index.html +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -2
- package/dist/runtime/better-auth/nuxt/chunk-reload.client.d.ts +2 -0
- package/dist/runtime/better-auth/nuxt/chunk-reload.client.js +41 -0
- package/dist/runtime/better-auth/vue/auth-client-loader.js +25 -4
- package/dist/runtime/better-auth/vue/cross-domain.js +2 -13
- package/dist/runtime/nuxt/function-name.js +9 -3
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +61 -16
- package/dist/runtime/vue/composables/use-upload-queue.js +28 -10
- package/dist/runtime/vue/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/CtC_CmOY.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.jOWrhpYd.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/CtC_CmOY.js"><script type="module" src="/__nuxt-convex-module/_nuxt/CtC_CmOY.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/C-J9qZjr.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"
|
|
1
|
+
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/CtC_CmOY.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.jOWrhpYd.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/CtC_CmOY.js"><script type="module" src="/__nuxt-convex-module/_nuxt/CtC_CmOY.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/C-J9qZjr.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"45bb65c1-9f29-4c02-bccc-cf2e35308dd6",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794326993439,false]</script></body></html>
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -535,7 +535,7 @@ function registerIntegrations(resolver, nuxt, options, policy, authRoute) {
|
|
|
535
535
|
const resolve = (key, pkg) => report(key, pkg, resolveIntegrationState(options[key], isPackageInstalled(pkg, rootDir), isDeclaredDependency(pkg, rootDir)));
|
|
536
536
|
const betterAuth = resolve("betterAuth", "@convex-dev/better-auth");
|
|
537
537
|
if (betterAuth) {
|
|
538
|
-
registerBetterAuth(resolver, authRoute, policy);
|
|
538
|
+
registerBetterAuth(resolver, authRoute, policy, nuxt.options.experimental);
|
|
539
539
|
} else {
|
|
540
540
|
registerBaseConvexClient(resolver);
|
|
541
541
|
}
|
|
@@ -684,9 +684,12 @@ const AUTH_PROXY_SECURITY_RULES = {
|
|
|
684
684
|
xssValidator: false,
|
|
685
685
|
allowedMethodsRestricter: { methods: ["GET", "HEAD", "POST", "OPTIONS"] }
|
|
686
686
|
};
|
|
687
|
-
function registerBetterAuth(resolver, authRoute, policy) {
|
|
687
|
+
function registerBetterAuth(resolver, authRoute, policy, experimental) {
|
|
688
688
|
addPlugin(resolver.resolve("./runtime/better-auth/vue/plugin.client"));
|
|
689
689
|
addPlugin(resolver.resolve("./runtime/better-auth/vue/plugin.server"));
|
|
690
|
+
if (experimental.emitRouteChunkError === "automatic" && experimental.appManifest) {
|
|
691
|
+
addPlugin(resolver.resolve("./runtime/better-auth/nuxt/chunk-reload.client"));
|
|
692
|
+
}
|
|
690
693
|
const noSessionVary = resolver.resolve("./runtime/better-auth/nuxt/no-session-vary");
|
|
691
694
|
addNitroPlugin({ nitro2: noSessionVary, nitro3: noSessionVary });
|
|
692
695
|
registerIntegrationImports(resolver, "betterAuth", policy);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineNuxtPlugin, reloadNuxtApp, useRuntimeConfig } from "#app";
|
|
2
|
+
import { onAuthClientLoadFailure } from "../vue/auth-client-loader.js";
|
|
3
|
+
function latestBuildURL(app) {
|
|
4
|
+
const root = (app.cdnURL || app.baseURL).replace(/\/+$/, "");
|
|
5
|
+
const dir = app.buildAssetsDir.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
6
|
+
return `${root}/${dir}/builds/latest.json?${Date.now()}`;
|
|
7
|
+
}
|
|
8
|
+
async function newerBuild(url, buildId) {
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
11
|
+
if (!response.ok) return void 0;
|
|
12
|
+
const latest = await response.json();
|
|
13
|
+
return typeof latest.id === "string" && latest.id !== buildId ? latest : void 0;
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export default defineNuxtPlugin({
|
|
19
|
+
name: "nuxt-convex-module:better-auth:chunk-reload",
|
|
20
|
+
setup(nuxtApp) {
|
|
21
|
+
const { app } = useRuntimeConfig();
|
|
22
|
+
const chunkErrors = /* @__PURE__ */ new WeakSet();
|
|
23
|
+
nuxtApp.hook("app:chunkError", ({ error }) => {
|
|
24
|
+
if (typeof error === "object" && error !== null) chunkErrors.add(error);
|
|
25
|
+
});
|
|
26
|
+
let checking = false;
|
|
27
|
+
let announced = false;
|
|
28
|
+
onAuthClientLoadFailure(({ error, used }) => {
|
|
29
|
+
if (checking) return;
|
|
30
|
+
checking = true;
|
|
31
|
+
void newerBuild(latestBuildURL(app), app.buildId).then((build) => {
|
|
32
|
+
checking = false;
|
|
33
|
+
if (!build || typeof error !== "object" || error === null || !chunkErrors.has(error)) return;
|
|
34
|
+
if (used) return reloadNuxtApp();
|
|
35
|
+
if (announced) return;
|
|
36
|
+
announced = true;
|
|
37
|
+
void nuxtApp.callHook("app:manifest:update", build);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -3,9 +3,15 @@ let loaded;
|
|
|
3
3
|
let loading;
|
|
4
4
|
let deferred = false;
|
|
5
5
|
const waiting = /* @__PURE__ */ new Set();
|
|
6
|
+
const failing = /* @__PURE__ */ new Set();
|
|
7
|
+
let used = false;
|
|
6
8
|
let evaluated;
|
|
7
9
|
let stopSignal;
|
|
8
10
|
export function loadAuthClient() {
|
|
11
|
+
used = true;
|
|
12
|
+
return load();
|
|
13
|
+
}
|
|
14
|
+
function load() {
|
|
9
15
|
if (evaluated) registerAuthClient(evaluated);
|
|
10
16
|
loading ??= import("#convex/auth-client").then(
|
|
11
17
|
({ authClient }) => {
|
|
@@ -14,6 +20,9 @@ export function loadAuthClient() {
|
|
|
14
20
|
},
|
|
15
21
|
(error) => {
|
|
16
22
|
loading = void 0;
|
|
23
|
+
const failure = { error, used };
|
|
24
|
+
used = false;
|
|
25
|
+
notify(failing, failure);
|
|
17
26
|
throw error;
|
|
18
27
|
}
|
|
19
28
|
);
|
|
@@ -33,11 +42,15 @@ export function registerAuthClient(client) {
|
|
|
33
42
|
evaluated = void 0;
|
|
34
43
|
loaded = client;
|
|
35
44
|
loading ??= Promise.resolve(client);
|
|
45
|
+
failing.clear();
|
|
36
46
|
const listeners = [...waiting];
|
|
37
47
|
waiting.clear();
|
|
38
|
-
|
|
48
|
+
notify(listeners, client);
|
|
49
|
+
}
|
|
50
|
+
function notify(listeners, value) {
|
|
51
|
+
for (const listener of [...listeners]) {
|
|
39
52
|
try {
|
|
40
|
-
listener(
|
|
53
|
+
listener(value);
|
|
41
54
|
} catch (error) {
|
|
42
55
|
report(error);
|
|
43
56
|
}
|
|
@@ -61,12 +74,18 @@ export function onAuthClient(listener) {
|
|
|
61
74
|
waiting.add(listener);
|
|
62
75
|
return () => waiting.delete(listener);
|
|
63
76
|
}
|
|
77
|
+
export function onAuthClientLoadFailure(listener) {
|
|
78
|
+
if (loaded) return () => {
|
|
79
|
+
};
|
|
80
|
+
failing.add(listener);
|
|
81
|
+
return () => failing.delete(listener);
|
|
82
|
+
}
|
|
64
83
|
export function deferAuthClient() {
|
|
65
84
|
if (deferred || loaded) return;
|
|
66
85
|
deferred = true;
|
|
67
86
|
const onVisible = () => {
|
|
68
87
|
if (document.visibilityState !== "visible") return;
|
|
69
|
-
void
|
|
88
|
+
void load().then(() => document.removeEventListener("visibilitychange", onVisible), () => {
|
|
70
89
|
});
|
|
71
90
|
};
|
|
72
91
|
document.addEventListener("visibilitychange", onVisible);
|
|
@@ -75,7 +94,7 @@ export function retryAuthClientLoad() {
|
|
|
75
94
|
if (loaded || typeof window === "undefined") return;
|
|
76
95
|
const retry = () => {
|
|
77
96
|
if (document.visibilityState !== "visible") return;
|
|
78
|
-
void
|
|
97
|
+
void load().then(() => {
|
|
79
98
|
window.removeEventListener("online", retry);
|
|
80
99
|
document.removeEventListener("visibilitychange", retry);
|
|
81
100
|
}, () => {
|
|
@@ -183,4 +202,6 @@ export function __resetAuthClientLoaderForTests() {
|
|
|
183
202
|
stopSignal = void 0;
|
|
184
203
|
evaluated = void 0;
|
|
185
204
|
waiting.clear();
|
|
205
|
+
failing.clear();
|
|
206
|
+
used = false;
|
|
186
207
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadAuthClient
|
|
1
|
+
import { loadAuthClient } from "./auth-client-loader.js";
|
|
2
2
|
const normalizePathname = (path) => {
|
|
3
3
|
const slashed = path.startsWith("/") ? path : `/${path}`;
|
|
4
4
|
let end = slashed.length;
|
|
@@ -18,18 +18,7 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
|
|
|
18
18
|
const authClient = await loadAuthClient().catch(() => loadAuthClient()).catch((error) => {
|
|
19
19
|
console.warn("[nuxt-convex-module] Failed to load the Better Auth client:", error);
|
|
20
20
|
});
|
|
21
|
-
if (!authClient)
|
|
22
|
-
onAuthClient((client) => void exchangeKeptToken(client, token, url, options));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
await exchangeOneTimeToken(authClient, token, url, options);
|
|
26
|
-
}
|
|
27
|
-
async function exchangeKeptToken(authClient, token, url, options) {
|
|
28
|
-
const current = await Promise.resolve().then(() => authClient.getSession()).catch(() => void 0);
|
|
29
|
-
if (!current || current.error || current.data?.session) {
|
|
30
|
-
if (!current?.data?.session) console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", current?.error ?? "the session lookup failed");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
21
|
+
if (!authClient) return;
|
|
33
22
|
await exchangeOneTimeToken(authClient, token, url, options);
|
|
34
23
|
}
|
|
35
24
|
async function exchangeOneTimeToken(authClient, token, url, options) {
|
|
@@ -20,7 +20,13 @@ function name(error, prefix) {
|
|
|
20
20
|
if (error.message !== `${prefix}${original}`) return;
|
|
21
21
|
unnamed.set(error, { message: original, stack });
|
|
22
22
|
const header = `${error.name}: ${original}`;
|
|
23
|
-
if (original && stack?.startsWith(header)) error
|
|
23
|
+
if (original && stack?.startsWith(header)) setStack(error, `${error.name}: ${error.message}${stack.slice(header.length)}`);
|
|
24
|
+
}
|
|
25
|
+
function setStack(error, stack) {
|
|
26
|
+
try {
|
|
27
|
+
error.stack = stack;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
24
30
|
}
|
|
25
31
|
export function withoutFunctionName(error, read) {
|
|
26
32
|
const before = error instanceof Error ? unnamed.get(error) : void 0;
|
|
@@ -28,11 +34,11 @@ export function withoutFunctionName(error, read) {
|
|
|
28
34
|
const named = error;
|
|
29
35
|
const { message, stack } = named;
|
|
30
36
|
named.message = before.message;
|
|
31
|
-
named
|
|
37
|
+
setStack(named, before.stack);
|
|
32
38
|
try {
|
|
33
39
|
return read(error);
|
|
34
40
|
} finally {
|
|
35
41
|
named.message = message;
|
|
36
|
-
named
|
|
42
|
+
setStack(named, stack);
|
|
37
43
|
}
|
|
38
44
|
}
|
|
@@ -16,9 +16,12 @@ export type UploadItemStage = 'prepare' | 'upload' | 'after';
|
|
|
16
16
|
/**
|
|
17
17
|
* A single entry in the queue managed by {@link useUploadQueue}.
|
|
18
18
|
*
|
|
19
|
+
* @typeParam TData - The app's own data for the item, given to
|
|
20
|
+
* {@link VueUploadQueue.enqueue}. `undefined` when the queue takes none.
|
|
21
|
+
*
|
|
19
22
|
* @public
|
|
20
23
|
*/
|
|
21
|
-
export interface UploadQueueItem {
|
|
24
|
+
export interface UploadQueueItem<TData = undefined> {
|
|
22
25
|
/** Stable id for this queue entry (not the storage id). */
|
|
23
26
|
id: string;
|
|
24
27
|
/** The file or blob being uploaded. */
|
|
@@ -48,13 +51,26 @@ export interface UploadQueueItem {
|
|
|
48
51
|
* and for a preview.
|
|
49
52
|
*/
|
|
50
53
|
prepared: Blob | null;
|
|
54
|
+
/**
|
|
55
|
+
* The app's data for this item, as given to `enqueue`: what the file is
|
|
56
|
+
* for (a document id, a kind, a position). Every hook reads it off the
|
|
57
|
+
* item. Deeply reactive like the rest of the item: an object reads as
|
|
58
|
+
* Vue's proxy of the one given, not a copy, so a change through it
|
|
59
|
+
* reaches that object and every item the same call queued (`toRaw`
|
|
60
|
+
* returns the object itself). A ref held as one of its properties reads
|
|
61
|
+
* unwrapped; one inside an array or a `Map` stays a ref. `undefined`
|
|
62
|
+
* when `enqueue` was given none.
|
|
63
|
+
*/
|
|
64
|
+
data: TData;
|
|
51
65
|
}
|
|
52
66
|
/**
|
|
53
67
|
* Options for {@link useUploadQueue}.
|
|
54
68
|
*
|
|
69
|
+
* @typeParam TData - The data each item carries; see {@link UploadQueueItem}.
|
|
70
|
+
*
|
|
55
71
|
* @public
|
|
56
72
|
*/
|
|
57
|
-
export interface UseUploadQueueOptions {
|
|
73
|
+
export interface UseUploadQueueOptions<TData = undefined> {
|
|
58
74
|
/** Maximum number of uploads to run at once. Defaults to `3`. */
|
|
59
75
|
concurrency?: number;
|
|
60
76
|
/**
|
|
@@ -63,7 +79,7 @@ export interface UseUploadQueueOptions {
|
|
|
63
79
|
* item ends `error`, refused when `isRefusal` says so. Runs once per
|
|
64
80
|
* item: a retry reuses what it returned.
|
|
65
81
|
*/
|
|
66
|
-
prepare?: (file: Blob, item: UploadQueueItem
|
|
82
|
+
prepare?: (file: Blob, item: UploadQueueItem<TData>, signal: AbortSignal) => Blob | Promise<Blob>;
|
|
67
83
|
/**
|
|
68
84
|
* Runs once the file is stored, before the item counts as a success:
|
|
69
85
|
* attach the storage id to a document, run a check on it. The item stays
|
|
@@ -74,7 +90,7 @@ export interface UseUploadQueueOptions {
|
|
|
74
90
|
* in error, so `onItemError` can delete it; the queue never deletes files,
|
|
75
91
|
* and a retry forgets it before uploading again.
|
|
76
92
|
*/
|
|
77
|
-
after?: (storageId: StorageId, item: UploadQueueItem
|
|
93
|
+
after?: (storageId: StorageId, item: UploadQueueItem<TData>, signal: AbortSignal) => void | Promise<void>;
|
|
78
94
|
/**
|
|
79
95
|
* Whether an error is a refusal (the app turned the file away) rather than
|
|
80
96
|
* a failure (the network, the deployment, an abort). Defaults to: the
|
|
@@ -82,30 +98,46 @@ export interface UseUploadQueueOptions {
|
|
|
82
98
|
* An aborted item is never refused, and neither is an error this check
|
|
83
99
|
* throws on.
|
|
84
100
|
*/
|
|
85
|
-
isRefusal?: (error: Error, item: UploadQueueItem) => boolean;
|
|
101
|
+
isRefusal?: (error: Error, item: UploadQueueItem<TData>) => boolean;
|
|
86
102
|
/**
|
|
87
103
|
* Called when an individual item finishes uploading (after `after`). A
|
|
88
104
|
* throw ends the item `error` with its storage id kept; a retry then runs
|
|
89
105
|
* only this callback again, never `after` a second time.
|
|
90
106
|
*/
|
|
91
|
-
onItemSuccess?: (storageId: StorageId, item: UploadQueueItem) => void | Promise<void>;
|
|
107
|
+
onItemSuccess?: (storageId: StorageId, item: UploadQueueItem<TData>) => void | Promise<void>;
|
|
92
108
|
/** Called when an individual item fails or is refused (`item.refused` says which). */
|
|
93
|
-
onItemError?: (error: Error, item: UploadQueueItem) => void;
|
|
94
|
-
/**
|
|
95
|
-
|
|
109
|
+
onItemError?: (error: Error, item: UploadQueueItem<TData>) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Called once every queued item has settled (succeeded or failed), after
|
|
112
|
+
* every `onItemSuccess` has returned.
|
|
113
|
+
*/
|
|
114
|
+
onComplete?: (items: UploadQueueItem<TData>[]) => void;
|
|
96
115
|
}
|
|
97
116
|
/** Accepted inputs to {@link VueUploadQueue.enqueue}. @public */
|
|
98
117
|
export type EnqueueInput = Blob | Blob[] | FileList | null | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The `data` argument of {@link VueUploadQueue.enqueue}: optional when the
|
|
120
|
+
* item data may be `undefined`, required otherwise.
|
|
121
|
+
*
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
export type EnqueueData<TData> = undefined extends TData ? [data?: TData] : [data: TData];
|
|
99
125
|
/**
|
|
100
126
|
* The reactive queue returned by {@link useUploadQueue}.
|
|
101
127
|
*
|
|
128
|
+
* @typeParam TData - The data each item carries; see {@link UploadQueueItem}.
|
|
129
|
+
*
|
|
102
130
|
* @public
|
|
103
131
|
*/
|
|
104
|
-
export interface VueUploadQueue {
|
|
132
|
+
export interface VueUploadQueue<TData = undefined> {
|
|
105
133
|
/** Reactive list of queued items, in insertion order. */
|
|
106
|
-
items: Readonly<Ref<UploadQueueItem[]>>;
|
|
107
|
-
/**
|
|
108
|
-
|
|
134
|
+
items: Readonly<Ref<UploadQueueItem<TData>[]>>;
|
|
135
|
+
/**
|
|
136
|
+
* Add one or more files to the queue; uploading starts automatically.
|
|
137
|
+
* `data` goes on every item this call queues, as `item.data`: queue files
|
|
138
|
+
* one call at a time to give each its own.
|
|
139
|
+
*/
|
|
140
|
+
enqueue: (files: EnqueueInput, ...data: EnqueueData<TData>) => void;
|
|
109
141
|
/** `true` while any item is pending or uploading. */
|
|
110
142
|
isUploading: Readonly<Ref<boolean>>;
|
|
111
143
|
/** Aggregate progress across all items, from `0` to `1`. */
|
|
@@ -114,11 +146,17 @@ export interface VueUploadQueue {
|
|
|
114
146
|
pendingCount: Readonly<Ref<number>>;
|
|
115
147
|
/** Number of items currently uploading. */
|
|
116
148
|
activeCount: Readonly<Ref<number>>;
|
|
117
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Abort all in-flight uploads. Queued items stay `pending` until the next
|
|
151
|
+
* `enqueue` or `retry()`, which start them again.
|
|
152
|
+
*/
|
|
118
153
|
cancel: () => void;
|
|
119
154
|
/** Remove an item by id, aborting it first if it is uploading. */
|
|
120
155
|
remove: (id: string) => void;
|
|
121
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Abort everything and empty the queue. Once the aborted uploads end,
|
|
158
|
+
* `onComplete` gets `[]`, unless files were queued since.
|
|
159
|
+
*/
|
|
122
160
|
clear: () => void;
|
|
123
161
|
/**
|
|
124
162
|
* Queue an errored item again, or every errored item without an id. It
|
|
@@ -143,6 +181,11 @@ export interface VueUploadQueue {
|
|
|
143
181
|
* item says whether the app `refused` it or it failed, so the UI can show a
|
|
144
182
|
* reason for one and {@link VueUploadQueue.retry} the other.
|
|
145
183
|
*
|
|
184
|
+
* `enqueue(files, data)` attaches the app's own data to the items it
|
|
185
|
+
* queues, as `item.data`, so a hook knows what a file is for without a
|
|
186
|
+
* side map: type it with the `TData` parameter,
|
|
187
|
+
* `useUploadQueue<{ albumId: Id<'albums'> }>(…)`.
|
|
188
|
+
*
|
|
146
189
|
* @example
|
|
147
190
|
* ```vue
|
|
148
191
|
* <script setup lang="ts">
|
|
@@ -171,6 +214,8 @@ export interface VueUploadQueue {
|
|
|
171
214
|
* </template>
|
|
172
215
|
* ```
|
|
173
216
|
*
|
|
217
|
+
* @typeParam TData - The data each item carries, given to `enqueue`;
|
|
218
|
+
* `undefined` (no data) by default.
|
|
174
219
|
* @param generateUploadUrl - A `FunctionReference` for the public
|
|
175
220
|
* `generateUploadUrl` mutation.
|
|
176
221
|
* @param options - Optional {@link UseUploadQueueOptions}.
|
|
@@ -178,6 +223,6 @@ export interface VueUploadQueue {
|
|
|
178
223
|
*
|
|
179
224
|
* @public
|
|
180
225
|
*/
|
|
181
|
-
export declare function useUploadQueue(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions): VueUploadQueue
|
|
226
|
+
export declare function useUploadQueue<TData = undefined>(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions<TData>): VueUploadQueue<TData>;
|
|
182
227
|
/** @public */
|
|
183
228
|
export declare const useConvexUploadQueue: typeof useUploadQueue;
|
|
@@ -19,6 +19,10 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
19
19
|
const controllers = /* @__PURE__ */ new Map();
|
|
20
20
|
const afterDone = /* @__PURE__ */ new Set();
|
|
21
21
|
const resend = /* @__PURE__ */ new Set();
|
|
22
|
+
let running = 0;
|
|
23
|
+
let generation = 0;
|
|
24
|
+
let cutOff = 0;
|
|
25
|
+
let held = false;
|
|
22
26
|
const activeCount = computed(
|
|
23
27
|
() => items.value.filter((item) => item.status === "uploading").length
|
|
24
28
|
);
|
|
@@ -33,11 +37,9 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
33
37
|
});
|
|
34
38
|
function pump() {
|
|
35
39
|
if (typeof XMLHttpRequest === "undefined") return;
|
|
36
|
-
let slots = concurrency - activeCount.value;
|
|
37
40
|
for (const item of items.value) {
|
|
38
|
-
if (
|
|
41
|
+
if (held || activeCount.value >= concurrency) break;
|
|
39
42
|
if (item.status !== "pending") continue;
|
|
40
|
-
slots--;
|
|
41
43
|
void run(item);
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -80,6 +82,8 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
80
82
|
options.onItemError?.(failure, item);
|
|
81
83
|
}
|
|
82
84
|
async function run(item) {
|
|
85
|
+
const own = generation;
|
|
86
|
+
running++;
|
|
83
87
|
item.status = "uploading";
|
|
84
88
|
const controller = new AbortController();
|
|
85
89
|
controllers.set(item.id, controller);
|
|
@@ -89,8 +93,8 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
89
93
|
if (options.after && !afterDone.has(item.id)) {
|
|
90
94
|
item.stage = "after";
|
|
91
95
|
await options.after(storageId, item, signal);
|
|
92
|
-
signal.throwIfAborted();
|
|
93
96
|
afterDone.add(item.id);
|
|
97
|
+
signal.throwIfAborted();
|
|
94
98
|
}
|
|
95
99
|
item.stage = null;
|
|
96
100
|
item.status = "success";
|
|
@@ -98,15 +102,20 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
98
102
|
} catch (caught) {
|
|
99
103
|
fail(item, caught, signal);
|
|
100
104
|
} finally {
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
options.onComplete?.(items.value);
|
|
105
|
+
if (own !== generation) {
|
|
106
|
+
if (--cutOff === 0 && items.value.length === 0) options.onComplete?.([]);
|
|
104
107
|
} else {
|
|
105
|
-
|
|
108
|
+
running--;
|
|
109
|
+
if (controllers.get(item.id) === controller) controllers.delete(item.id);
|
|
110
|
+
if (pendingCount.value === 0 && running === 0) {
|
|
111
|
+
options.onComplete?.(items.value);
|
|
112
|
+
} else if (!signal.aborted) {
|
|
113
|
+
pump();
|
|
114
|
+
}
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
}
|
|
109
|
-
function enqueue(files) {
|
|
118
|
+
function enqueue(files, ...[data]) {
|
|
110
119
|
const incoming = normalizeFiles(files);
|
|
111
120
|
if (incoming.length === 0) return;
|
|
112
121
|
items.value.push(
|
|
@@ -119,12 +128,16 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
119
128
|
error: null,
|
|
120
129
|
refused: false,
|
|
121
130
|
stage: null,
|
|
122
|
-
prepared: null
|
|
131
|
+
prepared: null,
|
|
132
|
+
// `undefined` only when `TData` allows it: `EnqueueData` requires it otherwise.
|
|
133
|
+
data
|
|
123
134
|
}))
|
|
124
135
|
);
|
|
136
|
+
held = false;
|
|
125
137
|
pump();
|
|
126
138
|
}
|
|
127
139
|
function cancel() {
|
|
140
|
+
held = true;
|
|
128
141
|
for (const controller of controllers.values()) {
|
|
129
142
|
controller.abort();
|
|
130
143
|
}
|
|
@@ -135,6 +148,7 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
135
148
|
afterDone.delete(id);
|
|
136
149
|
resend.delete(id);
|
|
137
150
|
items.value = items.value.filter((item) => item.id !== id);
|
|
151
|
+
pump();
|
|
138
152
|
}
|
|
139
153
|
function retry(id) {
|
|
140
154
|
for (const item of items.value) {
|
|
@@ -145,10 +159,14 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
|
|
|
145
159
|
if (resend.delete(item.id)) item.storageId = null;
|
|
146
160
|
if (item.storageId === null) item.progress = 0;
|
|
147
161
|
}
|
|
162
|
+
held = false;
|
|
148
163
|
pump();
|
|
149
164
|
}
|
|
150
165
|
function clear() {
|
|
151
166
|
cancel();
|
|
167
|
+
generation++;
|
|
168
|
+
cutOff += running;
|
|
169
|
+
running = 0;
|
|
152
170
|
controllers.clear();
|
|
153
171
|
afterDone.clear();
|
|
154
172
|
resend.clear();
|
|
@@ -88,6 +88,6 @@ export type { ConnectionState, OptimisticUpdate, QueryJournal, } from 'convex/br
|
|
|
88
88
|
export type { FunctionReference, FunctionReference_future, FunctionArgs, FunctionReturnType, OptionalRestArgs, ArgsAndOptions, } from 'convex/server';
|
|
89
89
|
export type { Value } from 'convex/values';
|
|
90
90
|
export { useUpload, useConvexUpload, uploadFile, type VueUpload, type UseUploadOptions, type UploadFileOptions, type GenerateUploadUrl, type StorageId, } from './composables/use-upload.js';
|
|
91
|
-
export { useUploadQueue, useConvexUploadQueue, type VueUploadQueue, type UseUploadQueueOptions, type UploadQueueItem, type UploadItemStage, type UploadItemStatus, type EnqueueInput, } from './composables/use-upload-queue.js';
|
|
91
|
+
export { useUploadQueue, useConvexUploadQueue, type VueUploadQueue, type UseUploadQueueOptions, type UploadQueueItem, type UploadItemStage, type UploadItemStatus, type EnqueueInput, type EnqueueData, } from './composables/use-upload-queue.js';
|
|
92
92
|
export { useStorageUrl, useConvexStorageUrl, type GetStorageUrl, } from './composables/use-storage-url.js';
|
|
93
93
|
export { ConvexImage } from './components/convex-image.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "nuxt-convex-module",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.2",
|
|
5
5
|
"description": "Convex backend integration with live queries, mutations, actions, pagination, file storage and SSR, auto-imported and typed against your deployment. The same Vue client runs without Nuxt; Better Auth, Clerk, Auth0 and Polar are opt-in.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"changelogen": "^0.6.2",
|
|
222
222
|
"convex": "^1.49.0",
|
|
223
223
|
"eslint": "^10.13.0",
|
|
224
|
-
"fallow": "3.
|
|
224
|
+
"fallow": "3.41.0",
|
|
225
225
|
"happy-dom": "^21.0.4",
|
|
226
226
|
"installed-check": "^11.0.0",
|
|
227
227
|
"nuxt": "^4.6.3",
|