use-convex 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.
Files changed (87) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +321 -0
  3. package/dist/module.d.mts +97 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +232 -0
  6. package/dist/runtime/components/AuthLoading.d.vue.ts +13 -0
  7. package/dist/runtime/components/AuthLoading.vue +8 -0
  8. package/dist/runtime/components/AuthLoading.vue.d.ts +13 -0
  9. package/dist/runtime/components/AuthRefreshing.d.vue.ts +13 -0
  10. package/dist/runtime/components/AuthRefreshing.vue +8 -0
  11. package/dist/runtime/components/AuthRefreshing.vue.d.ts +13 -0
  12. package/dist/runtime/components/Authenticated.d.vue.ts +13 -0
  13. package/dist/runtime/components/Authenticated.vue +8 -0
  14. package/dist/runtime/components/Authenticated.vue.d.ts +13 -0
  15. package/dist/runtime/components/Unauthenticated.d.vue.ts +13 -0
  16. package/dist/runtime/components/Unauthenticated.vue +8 -0
  17. package/dist/runtime/components/Unauthenticated.vue.d.ts +13 -0
  18. package/dist/runtime/composables/prewarmQuery.d.ts +9 -0
  19. package/dist/runtime/composables/prewarmQuery.js +14 -0
  20. package/dist/runtime/composables/useAuth.d.ts +60 -0
  21. package/dist/runtime/composables/useAuth.js +426 -0
  22. package/dist/runtime/composables/useAuthToken.d.ts +24 -0
  23. package/dist/runtime/composables/useAuthToken.js +71 -0
  24. package/dist/runtime/composables/useConvex.d.ts +5 -0
  25. package/dist/runtime/composables/useConvex.js +10 -0
  26. package/dist/runtime/composables/useConvexAction.d.ts +13 -0
  27. package/dist/runtime/composables/useConvexAction.js +30 -0
  28. package/dist/runtime/composables/useConvexAuth.d.ts +51 -0
  29. package/dist/runtime/composables/useConvexAuth.js +96 -0
  30. package/dist/runtime/composables/useConvexConnectionState.d.ts +8 -0
  31. package/dist/runtime/composables/useConvexConnectionState.js +21 -0
  32. package/dist/runtime/composables/useConvexGate.d.ts +20 -0
  33. package/dist/runtime/composables/useConvexGate.js +21 -0
  34. package/dist/runtime/composables/useConvexMutation.d.ts +25 -0
  35. package/dist/runtime/composables/useConvexMutation.js +35 -0
  36. package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +43 -0
  37. package/dist/runtime/composables/useConvexPaginatedQuery.js +188 -0
  38. package/dist/runtime/composables/useConvexQueries.d.ts +27 -0
  39. package/dist/runtime/composables/useConvexQueries.js +84 -0
  40. package/dist/runtime/composables/useConvexQuery.d.ts +56 -0
  41. package/dist/runtime/composables/useConvexQuery.js +147 -0
  42. package/dist/runtime/plugin.auth.client.d.ts +8 -0
  43. package/dist/runtime/plugin.auth.client.js +41 -0
  44. package/dist/runtime/plugin.client.d.ts +7 -0
  45. package/dist/runtime/plugin.client.js +40 -0
  46. package/dist/runtime/plugin.devtools.client.d.ts +6 -0
  47. package/dist/runtime/plugin.devtools.client.js +16 -0
  48. package/dist/runtime/plugin.server.d.ts +7 -0
  49. package/dist/runtime/plugin.server.js +44 -0
  50. package/dist/runtime/server/api/convex/auth/session.d.ts +36 -0
  51. package/dist/runtime/server/api/convex/auth/session.js +95 -0
  52. package/dist/runtime/server/auth.d.ts +19 -0
  53. package/dist/runtime/server/auth.js +32 -0
  54. package/dist/runtime/server/authCookies.d.ts +9 -0
  55. package/dist/runtime/server/authCookies.js +86 -0
  56. package/dist/runtime/server/fetch.d.ts +43 -0
  57. package/dist/runtime/server/fetch.js +63 -0
  58. package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -0
  59. package/dist/runtime/server/routes/__convex_devtools.get.js +32 -0
  60. package/dist/runtime/server/sameOrigin.d.ts +14 -0
  61. package/dist/runtime/server/sameOrigin.js +33 -0
  62. package/dist/runtime/utils/authCookie.d.ts +21 -0
  63. package/dist/runtime/utils/authCookie.js +47 -0
  64. package/dist/runtime/utils/authGate.d.ts +11 -0
  65. package/dist/runtime/utils/authGate.js +9 -0
  66. package/dist/runtime/utils/authMutex.d.ts +15 -0
  67. package/dist/runtime/utils/authMutex.js +47 -0
  68. package/dist/runtime/utils/authState.d.ts +31 -0
  69. package/dist/runtime/utils/authState.js +35 -0
  70. package/dist/runtime/utils/authStorage.d.ts +55 -0
  71. package/dist/runtime/utils/authStorage.js +85 -0
  72. package/dist/runtime/utils/context.d.ts +53 -0
  73. package/dist/runtime/utils/context.js +41 -0
  74. package/dist/runtime/utils/fetchToken.d.ts +9 -0
  75. package/dist/runtime/utils/fetchToken.js +6 -0
  76. package/dist/runtime/utils/http.d.ts +10 -0
  77. package/dist/runtime/utils/http.js +13 -0
  78. package/dist/runtime/utils/oauthRedirect.d.ts +5 -0
  79. package/dist/runtime/utils/oauthRedirect.js +12 -0
  80. package/dist/runtime/utils/overlay.d.ts +18 -0
  81. package/dist/runtime/utils/overlay.js +48 -0
  82. package/dist/runtime/utils/paginatedOptimistic.d.ts +37 -0
  83. package/dist/runtime/utils/paginatedOptimistic.js +178 -0
  84. package/dist/runtime/utils/queryKey.d.ts +8 -0
  85. package/dist/runtime/utils/queryKey.js +11 -0
  86. package/dist/types.d.mts +29 -0
  87. package/package.json +66 -0
@@ -0,0 +1,71 @@
1
+ import { navigateTo } from "nuxt/app";
2
+ import {
3
+ computed,
4
+ ref,
5
+ watch
6
+ } from "vue";
7
+ import { useConvexAuth } from "./useConvexAuth.js";
8
+ import { tryUseConvexContext } from "../utils/context.js";
9
+ export function useAuthToken() {
10
+ const token = ref(null);
11
+ const ctx = tryUseConvexContext();
12
+ if (!ctx) {
13
+ return computed(() => null);
14
+ }
15
+ const auth = useConvexAuth();
16
+ const refresh = async () => {
17
+ if (!ctx.client) {
18
+ token.value = null;
19
+ return;
20
+ }
21
+ try {
22
+ const claims = ctx.client.getAuth();
23
+ if (claims?.token) {
24
+ token.value = claims.token;
25
+ return;
26
+ }
27
+ } catch {
28
+ }
29
+ if (!auth.isAuthenticated.value) {
30
+ token.value = null;
31
+ return;
32
+ }
33
+ const fetchToken = ctx.auth.fetchToken;
34
+ if (!fetchToken) {
35
+ token.value = null;
36
+ return;
37
+ }
38
+ try {
39
+ token.value = await fetchToken({ forceRefreshToken: false }) ?? null;
40
+ } catch {
41
+ token.value = null;
42
+ }
43
+ };
44
+ watch(
45
+ [
46
+ () => auth.isAuthenticated.value,
47
+ () => auth.isRefreshing.value,
48
+ () => auth.isLoading.value
49
+ ],
50
+ () => {
51
+ void refresh();
52
+ },
53
+ { immediate: true }
54
+ );
55
+ return computed(() => token.value);
56
+ }
57
+ export function requireConvexAuthMiddleware(options = {}) {
58
+ const redirectTo = options.redirectTo ?? "/login";
59
+ const ctx = tryUseConvexContext();
60
+ if (!ctx) {
61
+ return navigateTo(redirectTo);
62
+ }
63
+ const auth = useConvexAuth();
64
+ if (auth.showAuthedUi.value) {
65
+ return;
66
+ }
67
+ if (auth.isLoading.value) {
68
+ return;
69
+ }
70
+ return navigateTo(redirectTo);
71
+ }
@@ -0,0 +1,5 @@
1
+ import type { ConvexClient } from 'convex/browser';
2
+ /**
3
+ * Returns the browser ConvexClient. Throws if called during SSR.
4
+ */
5
+ export declare function useConvex(): ConvexClient;
@@ -0,0 +1,10 @@
1
+ import { useConvexContext } from "../utils/context.js";
2
+ export function useConvex() {
3
+ const ctx = useConvexContext();
4
+ if (!ctx.client) {
5
+ throw new Error(
6
+ "[convex-nuxt] useConvex() is only available in the browser. Use fetchQuery on the server."
7
+ );
8
+ }
9
+ return ctx.client;
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
2
+ import { type MaybeRefOrGetter } from 'vue';
3
+ /**
4
+ * Browser action helper. Returns `{ run, error, pending }`.
5
+ *
6
+ * Safe to call during SSR setup — the ConvexClient is only touched when
7
+ * `run()` runs in the browser.
8
+ */
9
+ export declare function useConvexAction<Action extends FunctionReference<'action'>>(action: Action): {
10
+ run: (args?: MaybeRefOrGetter<FunctionArgs<Action>>) => Promise<FunctionReturnType<Action>>;
11
+ error: import("vue").Ref<Error | null, Error | null>;
12
+ pending: import("vue").ComputedRef<boolean>;
13
+ };
@@ -0,0 +1,30 @@
1
+ import { computed, ref, toValue } from "vue";
2
+ import { useConvexContext } from "../utils/context.js";
3
+ export function useConvexAction(action) {
4
+ const ctx = useConvexContext();
5
+ const error = ref(null);
6
+ const pendingCount = ref(0);
7
+ const run = async (args = {}) => {
8
+ if (import.meta.server || !ctx.client) {
9
+ throw new Error(
10
+ "[convex-nuxt] useConvexAction can only run in the browser."
11
+ );
12
+ }
13
+ pendingCount.value++;
14
+ error.value = null;
15
+ try {
16
+ return await ctx.client.action(action, toValue(args));
17
+ } catch (cause) {
18
+ const err = cause instanceof Error ? cause : new Error(String(cause));
19
+ error.value = err;
20
+ throw err;
21
+ } finally {
22
+ pendingCount.value--;
23
+ }
24
+ };
25
+ return {
26
+ run,
27
+ error,
28
+ pending: computed(() => pendingCount.value > 0)
29
+ };
30
+ }
@@ -0,0 +1,51 @@
1
+ import type { AuthTokenFetcher } from 'convex/browser';
2
+ import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
+ export type { AuthTokenFetcher };
4
+ export interface UseConvexAuthSetupOptions {
5
+ /**
6
+ * Async function returning a JWT (or null). Called by ConvexClient when
7
+ * the current token expires or is rejected. Pass `{ forceRefreshToken: true }`
8
+ * when a fresh token is required.
9
+ */
10
+ fetchToken: AuthTokenFetcher;
11
+ /**
12
+ * Auth provider is still resolving its initial session.
13
+ * @default false
14
+ */
15
+ isLoading?: MaybeRefOrGetter<boolean>;
16
+ /**
17
+ * Auth provider believes a user is signed in (has a session / tokens).
18
+ * Defaults to `true` so a bare `fetchToken` is enough for simple demos —
19
+ * Convex confirmation still gates `isAuthenticated`.
20
+ * @default true
21
+ */
22
+ isAuthenticated?: MaybeRefOrGetter<boolean>;
23
+ }
24
+ export interface UseConvexAuthReturn {
25
+ isLoading: ComputedRef<boolean>;
26
+ isAuthenticated: ComputedRef<boolean>;
27
+ isRefreshing: ComputedRef<boolean>;
28
+ /**
29
+ * JWT cookie is present. Use with `isAuthenticated` to keep an SSR-gated
30
+ * shell mounted while Convex confirms — do not live-subscribe on this alone.
31
+ */
32
+ hasSsrSession: ComputedRef<boolean>;
33
+ /**
34
+ * Mount the signed-in shell when the SSR cookie is present **or** Convex
35
+ * has confirmed auth. Prefer this over `isAuthenticated` alone so SSR HTML
36
+ * does not flash the signed-out gate during client confirmation.
37
+ *
38
+ * Live subscriptions must still gate on `isAuthenticated` (use `'skip'`).
39
+ */
40
+ showAuthedUi: ComputedRef<boolean>;
41
+ }
42
+ /**
43
+ * Provider-agnostic Convex auth state.
44
+ *
45
+ * Call once with `{ fetchToken }` (typically in a client plugin) to wire
46
+ * `ConvexClient.setAuth` + token refresh. Call without args anywhere to read
47
+ * `{ isLoading, isAuthenticated, isRefreshing, hasSsrSession }`.
48
+ *
49
+ * Same contract as React `useConvexAuth` / `ConvexProviderWithAuth`.
50
+ */
51
+ export declare function useConvexAuth(setup?: UseConvexAuthSetupOptions): UseConvexAuthReturn;
@@ -0,0 +1,96 @@
1
+ import {
2
+ computed,
3
+ toValue,
4
+ watch
5
+ } from "vue";
6
+ import { useHasSsrSessionRef } from "../utils/authCookie.js";
7
+ import { useConvexContext } from "../utils/context.js";
8
+ import { resolveConvexAuthState } from "../utils/authState.js";
9
+ export function useConvexAuth(setup) {
10
+ const ctx = useConvexContext();
11
+ const auth = ctx.auth;
12
+ const hasSsrSession = useHasSsrSessionRef();
13
+ const showAuthedUi = computed(
14
+ () => auth.isAuthenticated.value || hasSsrSession.value
15
+ );
16
+ if (setup) {
17
+ if (import.meta.server) {
18
+ return {
19
+ isLoading: computed(() => auth.isLoading.value),
20
+ isAuthenticated: computed(() => auth.isAuthenticated.value),
21
+ isRefreshing: computed(() => auth.isRefreshing.value),
22
+ hasSsrSession,
23
+ showAuthedUi
24
+ };
25
+ }
26
+ const client = ctx.client;
27
+ if (!client) {
28
+ throw new Error(
29
+ "[convex-nuxt] useConvexAuth({ fetchToken }) requires ConvexClient (browser only)."
30
+ );
31
+ }
32
+ auth.fetchToken = setup.fetchToken;
33
+ auth.configured = true;
34
+ const syncFromProvider = () => {
35
+ const providerLoading = toValue(setup.isLoading) ?? false;
36
+ const providerAuthenticated = toValue(setup.isAuthenticated) ?? true;
37
+ auth.providerLoading.value = providerLoading;
38
+ auth.providerAuthenticated.value = providerAuthenticated;
39
+ if (providerLoading) {
40
+ auth.isConvexAuthenticated.value = null;
41
+ auth.isRefreshingRaw.value = false;
42
+ applyAuthState(auth);
43
+ return;
44
+ }
45
+ if (!providerAuthenticated) {
46
+ auth.isConvexAuthenticated.value = false;
47
+ auth.isRefreshingRaw.value = false;
48
+ try {
49
+ client.client.clearAuth();
50
+ } catch {
51
+ }
52
+ applyAuthState(auth);
53
+ return;
54
+ }
55
+ auth.isConvexAuthenticated.value = null;
56
+ applyAuthState(auth);
57
+ client.client.setAuth(
58
+ setup.fetchToken,
59
+ (backendReportsIsAuthenticated) => {
60
+ auth.isConvexAuthenticated.value = backendReportsIsAuthenticated;
61
+ applyAuthState(auth);
62
+ },
63
+ (isRefreshing) => {
64
+ auth.isRefreshingRaw.value = isRefreshing;
65
+ applyAuthState(auth);
66
+ }
67
+ );
68
+ };
69
+ watch(
70
+ () => [
71
+ toValue(setup.isLoading) ?? false,
72
+ toValue(setup.isAuthenticated) ?? true
73
+ ],
74
+ syncFromProvider,
75
+ { immediate: true }
76
+ );
77
+ }
78
+ return {
79
+ isLoading: computed(() => auth.isLoading.value),
80
+ isAuthenticated: computed(() => auth.isAuthenticated.value),
81
+ isRefreshing: computed(() => auth.isRefreshing.value),
82
+ hasSsrSession,
83
+ showAuthedUi
84
+ };
85
+ }
86
+ function applyAuthState(auth) {
87
+ const next = resolveConvexAuthState({
88
+ authProviderLoading: auth.providerLoading.value,
89
+ authProviderAuthenticated: auth.providerAuthenticated.value,
90
+ isConvexAuthenticated: auth.isConvexAuthenticated.value,
91
+ isRefreshing: auth.isRefreshingRaw.value
92
+ });
93
+ auth.isLoading.value = next.isLoading;
94
+ auth.isAuthenticated.value = next.isAuthenticated;
95
+ auth.isRefreshing.value = next.isRefreshing;
96
+ }
@@ -0,0 +1,8 @@
1
+ import type { ConnectionState } from 'convex/browser';
2
+ import { type ShallowRef } from 'vue';
3
+ export type { ConnectionState };
4
+ /**
5
+ * Reactive WebSocket {@link ConnectionState} for the browser ConvexClient.
6
+ * Returns `null` during SSR or when the client is unavailable.
7
+ */
8
+ export declare function useConvexConnectionState(): ShallowRef<ConnectionState | null>;
@@ -0,0 +1,21 @@
1
+ import { onScopeDispose, shallowRef } from "vue";
2
+ import { useConvexContext } from "../utils/context.js";
3
+ export function useConvexConnectionState() {
4
+ const state = shallowRef(null);
5
+ if (import.meta.server) {
6
+ return state;
7
+ }
8
+ const ctx = useConvexContext();
9
+ const client = ctx.client;
10
+ if (!client) {
11
+ return state;
12
+ }
13
+ state.value = client.connectionState();
14
+ const unsubscribe = client.subscribeToConnectionState((next) => {
15
+ state.value = next;
16
+ });
17
+ onScopeDispose(() => {
18
+ unsubscribe();
19
+ });
20
+ return state;
21
+ }
@@ -0,0 +1,20 @@
1
+ import { type ComputedRef } from 'vue';
2
+ export interface ConvexGate {
3
+ isLoading: ComputedRef<boolean>;
4
+ isAuthenticated: ComputedRef<boolean>;
5
+ isRefreshing: ComputedRef<boolean>;
6
+ hasSsrSession: ComputedRef<boolean>;
7
+ showAuthedUi: ComputedRef<boolean>;
8
+ /** True while resolving auth and the authed shell is not yet shown. */
9
+ showLoading: ComputedRef<boolean>;
10
+ /** True when signed out (no cookie, not authenticated, not loading). */
11
+ showSignedOut: ComputedRef<boolean>;
12
+ /** True while refreshing a rejected token for an authenticated session. */
13
+ showRefreshing: ComputedRef<boolean>;
14
+ }
15
+ /**
16
+ * Convenience gate flags for auth UI. Same values as the
17
+ * `Authenticated` / `Unauthenticated` / `AuthLoading` / `AuthRefreshing`
18
+ * components.
19
+ */
20
+ export declare function useConvexGate(): ConvexGate;
@@ -0,0 +1,21 @@
1
+ import { computed } from "vue";
2
+ import { useConvexAuth } from "../composables/useConvexAuth.js";
3
+ export function useConvexGate() {
4
+ const auth = useConvexAuth();
5
+ return {
6
+ isLoading: auth.isLoading,
7
+ isAuthenticated: auth.isAuthenticated,
8
+ isRefreshing: auth.isRefreshing,
9
+ hasSsrSession: auth.hasSsrSession,
10
+ showAuthedUi: auth.showAuthedUi,
11
+ showLoading: computed(
12
+ () => auth.isLoading.value && !auth.showAuthedUi.value
13
+ ),
14
+ showSignedOut: computed(
15
+ () => !auth.showAuthedUi.value && !auth.isLoading.value
16
+ ),
17
+ showRefreshing: computed(
18
+ () => auth.isAuthenticated.value && auth.isRefreshing.value
19
+ )
20
+ };
21
+ }
@@ -0,0 +1,25 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
2
+ import type { OptimisticUpdate } from 'convex/browser';
3
+ import { type MaybeRefOrGetter } from 'vue';
4
+ export type { OptimisticUpdate };
5
+ export interface UseConvexMutationOptions<Mutation extends FunctionReference<'mutation'>> {
6
+ /**
7
+ * Local query update applied while the mutation is in flight.
8
+ * Passed through to `ConvexClient.mutation` → `BaseConvexClient`.
9
+ */
10
+ optimisticUpdate?: OptimisticUpdate<FunctionArgs<Mutation>>;
11
+ }
12
+ /**
13
+ * Browser mutation helper. Returns `{ mutate, error, pending }`.
14
+ *
15
+ * Safe to call during SSR setup — the ConvexClient is only touched when
16
+ * `mutate()` runs in the browser.
17
+ *
18
+ * Pass `{ optimisticUpdate }` to apply temporary local query results while
19
+ * the mutation is in flight (same contract as React `withOptimisticUpdate`).
20
+ */
21
+ export declare function useConvexMutation<Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, options?: UseConvexMutationOptions<Mutation>): {
22
+ mutate: (args?: MaybeRefOrGetter<FunctionArgs<Mutation>>) => Promise<FunctionReturnType<Mutation>>;
23
+ error: import("vue").Ref<Error | null, Error | null>;
24
+ pending: import("vue").ComputedRef<boolean>;
25
+ };
@@ -0,0 +1,35 @@
1
+ import { computed, ref, toValue } from "vue";
2
+ import { useConvexContext } from "../utils/context.js";
3
+ export function useConvexMutation(mutation, options = {}) {
4
+ const ctx = useConvexContext();
5
+ const error = ref(null);
6
+ const pendingCount = ref(0);
7
+ const mutate = async (args = {}) => {
8
+ if (import.meta.server || !ctx.client) {
9
+ throw new Error(
10
+ "[convex-nuxt] useConvexMutation can only run in the browser."
11
+ );
12
+ }
13
+ pendingCount.value++;
14
+ error.value = null;
15
+ try {
16
+ const resolved = toValue(args);
17
+ return await ctx.client.mutation(
18
+ mutation,
19
+ resolved,
20
+ options.optimisticUpdate ? { optimisticUpdate: options.optimisticUpdate } : void 0
21
+ );
22
+ } catch (cause) {
23
+ const err = cause instanceof Error ? cause : new Error(String(cause));
24
+ error.value = err;
25
+ throw err;
26
+ } finally {
27
+ pendingCount.value--;
28
+ }
29
+ };
30
+ return {
31
+ mutate,
32
+ error,
33
+ pending: computed(() => pendingCount.value > 0)
34
+ };
35
+ }
@@ -0,0 +1,43 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType, PaginationOptions, PaginationResult } from 'convex/server';
2
+ import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
3
+ /**
4
+ * A query usable with {@link useConvexPaginatedQuery}.
5
+ * Must accept `paginationOpts` and return {@link PaginationResult}.
6
+ */
7
+ export type PaginatedQueryReference = FunctionReference<'query', 'public', {
8
+ paginationOpts: PaginationOptions;
9
+ }, PaginationResult<any>>;
10
+ export type PaginatedQueryArgs<Query extends PaginatedQueryReference> = Omit<FunctionArgs<Query>, 'paginationOpts'>;
11
+ export type PaginatedQueryItem<Query extends PaginatedQueryReference> = FunctionReturnType<Query>['page'][number];
12
+ export type PaginationStatus = 'LoadingFirstPage' | 'CanLoadMore' | 'LoadingMore' | 'Exhausted';
13
+ export interface UseConvexPaginatedQueryOptions {
14
+ /** Items to load on the first page (and default for `loadMore`). */
15
+ initialNumItems: number;
16
+ /** Explicit Nuxt payload key for the first page. */
17
+ key?: string;
18
+ /** SSR the first page via HttpClient. Defaults to `convex.server`. */
19
+ server?: boolean;
20
+ /** JWT for the SSR / HttpClient first page. */
21
+ token?: string;
22
+ /**
23
+ * Skip first-page HttpClient and live paginated subscribe until Convex
24
+ * confirms auth. Same contract as `useConvexQuery({ authenticated: true })`.
25
+ * @default false
26
+ */
27
+ authenticated?: boolean;
28
+ }
29
+ export type UseConvexPaginatedQueryReturn<Query extends PaginatedQueryReference> = {
30
+ results: ComputedRef<Array<PaginatedQueryItem<Query>>>;
31
+ status: ComputedRef<PaginationStatus>;
32
+ isLoading: ComputedRef<boolean>;
33
+ loadMore: (numItems?: number) => void;
34
+ error: Ref<Error | null>;
35
+ };
36
+ /**
37
+ * Paginated Convex query with SSR for the first page and live multi-page updates.
38
+ *
39
+ * On the browser this uses `ConvexClient.onPaginatedUpdate_experimental` so
40
+ * every loaded page stays reactive (React `usePaginatedQuery` parity). The SSR
41
+ * snapshot hydrates the first page until the live subscription is ready.
42
+ */
43
+ export declare function useConvexPaginatedQuery<Query extends PaginatedQueryReference>(query: Query, args: MaybeRefOrGetter<PaginatedQueryArgs<Query> | "skip"> | undefined, options: UseConvexPaginatedQueryOptions): Promise<UseConvexPaginatedQueryReturn<Query>>;
@@ -0,0 +1,188 @@
1
+ import { convexToJson, jsonToConvex } from "convex/values";
2
+ import { useAsyncData, useNuxtApp, useRuntimeConfig } from "nuxt/app";
3
+ import {
4
+ computed,
5
+ onScopeDispose,
6
+ ref,
7
+ shallowRef,
8
+ toValue,
9
+ watch
10
+ } from "vue";
11
+ import { resolveAuthGatedArgs } from "../utils/authGate.js";
12
+ import { useConvexContext } from "../utils/context.js";
13
+ import { convexQueryKey } from "../utils/queryKey.js";
14
+ function mapLiveStatus(status) {
15
+ switch (status) {
16
+ case "LoadingFirstPage":
17
+ return "LoadingFirstPage";
18
+ case "LoadingMore":
19
+ return "LoadingMore";
20
+ case "Exhausted":
21
+ return "Exhausted";
22
+ case "CanLoadMore":
23
+ return "CanLoadMore";
24
+ }
25
+ }
26
+ export async function useConvexPaginatedQuery(query, args = {}, options) {
27
+ if (options.initialNumItems <= 0) {
28
+ throw new Error(
29
+ "[convex-nuxt] useConvexPaginatedQuery initialNumItems must be > 0"
30
+ );
31
+ }
32
+ const runtimeConfig = useRuntimeConfig();
33
+ const defaultServer = runtimeConfig.public.convex?.server;
34
+ const server = options.server ?? defaultServer ?? true;
35
+ const requireAuth = options.authenticated ?? false;
36
+ const ctx = useConvexContext();
37
+ const resolveRawArgs = () => toValue(args);
38
+ const resolveEffectiveArgs = () => resolveAuthGatedArgs(resolveRawArgs(), {
39
+ authenticated: requireAuth,
40
+ isAuthenticated: ctx.auth.isAuthenticated.value
41
+ });
42
+ const firstPageArgsFrom = (base) => {
43
+ if (base === "skip") {
44
+ return "skip";
45
+ }
46
+ return {
47
+ ...base,
48
+ paginationOpts: {
49
+ numItems: options.initialNumItems,
50
+ cursor: null
51
+ }
52
+ };
53
+ };
54
+ const key = computed(
55
+ () => convexQueryKey(query, firstPageArgsFrom(resolveRawArgs()), options.key)
56
+ );
57
+ const asyncData = await useAsyncData(
58
+ key,
59
+ async () => {
60
+ const pageArgs = firstPageArgsFrom(resolveEffectiveArgs());
61
+ if (pageArgs === "skip") {
62
+ return null;
63
+ }
64
+ const token = options.token ?? ctx.ssrToken.value;
65
+ if (requireAuth && import.meta.client && !token) {
66
+ const nuxtApp = useNuxtApp();
67
+ const cached = nuxtApp.payload.data[toValue(key)];
68
+ return cached ?? null;
69
+ }
70
+ const http = ctx.createHttpClient({ token });
71
+ const result = await http.query(
72
+ query,
73
+ pageArgs
74
+ );
75
+ return jsonToConvex(convexToJson(result));
76
+ },
77
+ {
78
+ server,
79
+ watch: [
80
+ () => toValue(args),
81
+ () => options.token ?? ctx.ssrToken.value,
82
+ () => requireAuth && ctx.auth.isAuthenticated.value && !!(options.token ?? ctx.ssrToken.value)
83
+ ]
84
+ }
85
+ );
86
+ const live = shallowRef(null);
87
+ const liveReady = ref(false);
88
+ const error = ref(null);
89
+ const liveLoadMore = shallowRef(null);
90
+ const results = computed(() => {
91
+ if (resolveEffectiveArgs() === "skip") {
92
+ return asyncData.data.value?.page ?? [];
93
+ }
94
+ if (liveReady.value && live.value) {
95
+ return live.value.results;
96
+ }
97
+ return asyncData.data.value?.page ?? [];
98
+ });
99
+ const status = computed(() => {
100
+ if (resolveEffectiveArgs() === "skip") {
101
+ if (asyncData.data.value) {
102
+ return asyncData.data.value.isDone ? "Exhausted" : "CanLoadMore";
103
+ }
104
+ return "Exhausted";
105
+ }
106
+ if (liveReady.value && live.value) {
107
+ return mapLiveStatus(live.value.status);
108
+ }
109
+ if (asyncData.pending.value || import.meta.client && !liveReady.value) {
110
+ if (asyncData.data.value) {
111
+ return asyncData.data.value.isDone ? "Exhausted" : "CanLoadMore";
112
+ }
113
+ return "LoadingFirstPage";
114
+ }
115
+ if (asyncData.data.value) {
116
+ return asyncData.data.value.isDone ? "Exhausted" : "CanLoadMore";
117
+ }
118
+ return "LoadingFirstPage";
119
+ });
120
+ const isLoading = computed(
121
+ () => status.value === "LoadingFirstPage" || status.value === "LoadingMore"
122
+ );
123
+ if (import.meta.client && ctx.client) {
124
+ const client = ctx.client;
125
+ let cancel;
126
+ const subscribe = () => {
127
+ cancel?.();
128
+ cancel = void 0;
129
+ liveReady.value = false;
130
+ live.value = null;
131
+ liveLoadMore.value = null;
132
+ error.value = null;
133
+ const base = resolveEffectiveArgs();
134
+ if (base === "skip") {
135
+ liveReady.value = true;
136
+ return;
137
+ }
138
+ cancel = client.onPaginatedUpdate_experimental(
139
+ query,
140
+ base,
141
+ { initialNumItems: options.initialNumItems },
142
+ (result) => {
143
+ const page = result;
144
+ live.value = page;
145
+ liveLoadMore.value = page.loadMore;
146
+ liveReady.value = true;
147
+ error.value = null;
148
+ },
149
+ (err) => {
150
+ error.value = err;
151
+ liveReady.value = true;
152
+ }
153
+ );
154
+ };
155
+ watch(
156
+ () => [
157
+ toValue(args),
158
+ options.initialNumItems,
159
+ ctx.auth.isAuthenticated.value
160
+ ],
161
+ subscribe,
162
+ { immediate: true }
163
+ );
164
+ onScopeDispose(() => {
165
+ cancel?.();
166
+ });
167
+ }
168
+ const loadMore = (numItems = options.initialNumItems) => {
169
+ if (import.meta.server || !ctx.client) {
170
+ return;
171
+ }
172
+ if (status.value !== "CanLoadMore") {
173
+ return;
174
+ }
175
+ const fn = liveLoadMore.value;
176
+ if (!fn) {
177
+ return;
178
+ }
179
+ fn(numItems);
180
+ };
181
+ return {
182
+ results,
183
+ status,
184
+ isLoading,
185
+ loadMore,
186
+ error
187
+ };
188
+ }
@@ -0,0 +1,27 @@
1
+ import type { FunctionReference, FunctionReturnType } from 'convex/server';
2
+ import type { Value } from 'convex/values';
3
+ import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
4
+ export type ConvexQueriesRequest = Record<string, {
5
+ query: FunctionReference<'query'>;
6
+ args: Record<string, Value>;
7
+ } | 'skip'>;
8
+ export type ConvexQueriesResult<Request extends ConvexQueriesRequest> = {
9
+ [K in keyof Request]: Request[K] extends {
10
+ query: infer Query;
11
+ args: any;
12
+ } ? Query extends FunctionReference<'query'> ? FunctionReturnType<Query> | undefined | Error : undefined : undefined;
13
+ };
14
+ /**
15
+ * Subscribe to a dynamic map of Convex queries (React `useQueries` parity).
16
+ *
17
+ * Pass a reactive object whose values are `{ query, args }` or `'skip'`.
18
+ * Returns a computed map of the same keys → result | `undefined` (loading) |
19
+ * `Error`.
20
+ *
21
+ * Browser-only live subscriptions. During SSR every entry is `undefined`
22
+ * (combine with `useConvexQuery` when you need SSR for known queries).
23
+ *
24
+ * Unchanged keys (same function name + args) keep their existing subscription
25
+ * so deep reactive churn does not tear down WebSocket watches.
26
+ */
27
+ export declare function useConvexQueries<Request extends ConvexQueriesRequest>(queries: MaybeRefOrGetter<Request>): ComputedRef<ConvexQueriesResult<Request>>;