nuxt-convex-module 0.14.1 → 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.
@@ -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:"7121d13f-7e15-4436-95a8-70fef1a171ce",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794230621568,false]</script></body></html>
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
@@ -5,7 +5,7 @@
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.6.0"
7
7
  },
8
- "version": "0.14.1",
8
+ "version": "0.14.2",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
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,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -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
- for (const listener of listeners) {
48
+ notify(listeners, client);
49
+ }
50
+ function notify(listeners, value) {
51
+ for (const listener of [...listeners]) {
39
52
  try {
40
- listener(client);
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 loadAuthClient().then(() => document.removeEventListener("visibilitychange", onVisible), () => {
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 loadAuthClient().then(() => {
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
  }
@@ -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, signal: AbortSignal) => Blob | Promise<Blob>;
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, signal: AbortSignal) => void | Promise<void>;
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,33 +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;
109
+ onItemError?: (error: Error, item: UploadQueueItem<TData>) => void;
94
110
  /**
95
111
  * Called once every queued item has settled (succeeded or failed), after
96
112
  * every `onItemSuccess` has returned.
97
113
  */
98
- onComplete?: (items: UploadQueueItem[]) => void;
114
+ onComplete?: (items: UploadQueueItem<TData>[]) => void;
99
115
  }
100
116
  /** Accepted inputs to {@link VueUploadQueue.enqueue}. @public */
101
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];
102
125
  /**
103
126
  * The reactive queue returned by {@link useUploadQueue}.
104
127
  *
128
+ * @typeParam TData - The data each item carries; see {@link UploadQueueItem}.
129
+ *
105
130
  * @public
106
131
  */
107
- export interface VueUploadQueue {
132
+ export interface VueUploadQueue<TData = undefined> {
108
133
  /** Reactive list of queued items, in insertion order. */
109
- items: Readonly<Ref<UploadQueueItem[]>>;
110
- /** Add one or more files to the queue; uploading starts automatically. */
111
- enqueue: (files: EnqueueInput) => void;
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;
112
141
  /** `true` while any item is pending or uploading. */
113
142
  isUploading: Readonly<Ref<boolean>>;
114
143
  /** Aggregate progress across all items, from `0` to `1`. */
@@ -152,6 +181,11 @@ export interface VueUploadQueue {
152
181
  * item says whether the app `refused` it or it failed, so the UI can show a
153
182
  * reason for one and {@link VueUploadQueue.retry} the other.
154
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
+ *
155
189
  * @example
156
190
  * ```vue
157
191
  * <script setup lang="ts">
@@ -180,6 +214,8 @@ export interface VueUploadQueue {
180
214
  * </template>
181
215
  * ```
182
216
  *
217
+ * @typeParam TData - The data each item carries, given to `enqueue`;
218
+ * `undefined` (no data) by default.
183
219
  * @param generateUploadUrl - A `FunctionReference` for the public
184
220
  * `generateUploadUrl` mutation.
185
221
  * @param options - Optional {@link UseUploadQueueOptions}.
@@ -187,6 +223,6 @@ export interface VueUploadQueue {
187
223
  *
188
224
  * @public
189
225
  */
190
- export declare function useUploadQueue(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions): VueUploadQueue;
226
+ export declare function useUploadQueue<TData = undefined>(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions<TData>): VueUploadQueue<TData>;
191
227
  /** @public */
192
228
  export declare const useConvexUploadQueue: typeof useUploadQueue;
@@ -115,7 +115,7 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
115
115
  }
116
116
  }
117
117
  }
118
- function enqueue(files) {
118
+ function enqueue(files, ...[data]) {
119
119
  const incoming = normalizeFiles(files);
120
120
  if (incoming.length === 0) return;
121
121
  items.value.push(
@@ -128,7 +128,9 @@ export function useUploadQueue(generateUploadUrl, options = {}) {
128
128
  error: null,
129
129
  refused: false,
130
130
  stage: null,
131
- prepared: null
131
+ prepared: null,
132
+ // `undefined` only when `TData` allows it: `EnqueueData` requires it otherwise.
133
+ data
132
134
  }))
133
135
  );
134
136
  held = false;
@@ -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.1",
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.39.0",
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",