nuxt-convex-module 0.0.0 → 0.9.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.
- package/LICENSE +1 -1
- package/README.md +51 -157
- package/dist/chunks/index.mjs +1 -1
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/71bSMC-_.js +2 -0
- package/dist/devtools-client/_nuxt/{CZbAGcNC.js → BAaUCl_m.js} +1 -1
- package/dist/devtools-client/_nuxt/BhmjAr2Q.js +1 -0
- package/dist/devtools-client/_nuxt/BmBQNDID.js +1 -0
- package/dist/devtools-client/_nuxt/C6YdTgGl.js +2 -0
- package/dist/devtools-client/_nuxt/{DPWXUmcI.js → CKakiU_h.js} +1 -1
- package/dist/devtools-client/_nuxt/Cm7YqZQ-.js +1 -0
- package/dist/devtools-client/_nuxt/{dy_xXcv9.js → DQmVr-EY.js} +1 -1
- package/dist/devtools-client/_nuxt/D_Rij7MD.js +1 -0
- package/dist/devtools-client/_nuxt/EZ4-8Xzm.js +3 -0
- package/dist/devtools-client/_nuxt/Kba0XpjH.js +1 -0
- package/dist/devtools-client/_nuxt/OVE7s6ty.js +1 -0
- package/dist/devtools-client/_nuxt/_Lf3mfPR.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/7dfea6da-3eca-4a35-ba06-5e069ac7e8df.json +1 -0
- package/dist/devtools-client/_nuxt/entry.HF8C6P11.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CLlwiMp-.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.Bxr6fpDU.css +1 -0
- package/dist/devtools-client/_nuxt/queries.DNBUjWk2.css +1 -0
- package/dist/devtools-client/_nuxt/vZZlEX5A.js +1 -0
- package/dist/devtools-client/auth/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/logs/index.html +1 -1
- package/dist/devtools-client/queries/index.html +1 -1
- package/dist/module.d.mts +129 -0
- package/dist/module.json +2 -2
- package/dist/module.mjs +576 -0
- package/dist/runtime/auth0/vue/index.d.ts +3 -3
- package/dist/runtime/better-auth/nuxt/middleware.d.ts +4 -2
- package/dist/runtime/better-auth/nuxt/middleware.js +15 -12
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +18 -2
- package/dist/runtime/better-auth/nuxt/proxy.js +7 -7
- package/dist/runtime/better-auth/nuxt/server.d.ts +6 -6
- package/dist/runtime/better-auth/nuxt/server.js +15 -0
- package/dist/runtime/better-auth/vue/auth-boundary.d.ts +3 -1
- package/dist/runtime/better-auth/vue/client.d.ts +1 -1
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +4 -5
- package/dist/runtime/better-auth/vue/cross-domain.js +11 -4
- package/dist/runtime/better-auth/vue/hydration.d.ts +1 -1
- package/dist/runtime/better-auth/vue/index.d.ts +2 -1
- package/dist/runtime/better-auth/vue/index.js +2 -1
- package/dist/runtime/better-auth/vue/plugin.client.d.ts +1 -5
- package/dist/runtime/better-auth/vue/plugin.client.js +6 -10
- package/dist/runtime/better-auth/vue/plugin.server.d.ts +5 -16
- package/dist/runtime/better-auth/vue/plugin.server.js +3 -6
- package/dist/runtime/better-auth/vue/redirect.d.ts +33 -0
- package/dist/runtime/better-auth/vue/redirect.js +14 -0
- package/dist/runtime/better-auth/vue/{use-auth.d.ts → use-better-auth.d.ts} +31 -11
- package/dist/runtime/better-auth/vue/{use-auth.js → use-better-auth.js} +4 -3
- package/dist/runtime/clerk/vue/index.d.ts +4 -4
- package/dist/runtime/devtools/bridge.js +1 -1
- package/dist/runtime/nuxt/app.d.ts +48 -0
- package/dist/runtime/nuxt/app.js +8 -0
- package/dist/runtime/nuxt/composables/use-async-paginated-query.d.ts +88 -0
- package/dist/runtime/nuxt/composables/use-async-paginated-query.js +90 -0
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +7 -16
- package/dist/runtime/nuxt/composables/use-async-query.js +14 -9
- package/dist/runtime/nuxt/config.d.ts +11 -0
- package/dist/runtime/nuxt/config.js +1 -0
- package/dist/runtime/nuxt/csp.d.ts +44 -0
- package/dist/runtime/nuxt/csp.js +46 -0
- package/dist/runtime/nuxt/index.d.ts +8 -8
- package/dist/runtime/nuxt/security.d.ts +33 -0
- package/dist/runtime/nuxt/security.js +14 -0
- package/dist/runtime/polar/vue/components.d.ts +8 -1
- package/dist/runtime/polar/vue/components.js +3 -3
- package/dist/runtime/vue/auth/index.d.ts +4 -4
- package/dist/runtime/vue/auth/index.js +6 -2
- package/dist/runtime/vue/client.d.ts +5 -55
- package/dist/runtime/vue/client.js +4 -0
- package/dist/runtime/vue/components/convex-image.d.ts +50 -0
- package/dist/runtime/vue/components/convex-image.js +21 -0
- package/dist/runtime/vue/composables/use-connection-state.d.ts +1 -0
- package/dist/runtime/vue/composables/use-paginated-query.d.ts +5 -1
- package/dist/runtime/vue/composables/use-paginated-query.js +2 -2
- package/dist/runtime/vue/composables/use-query.d.ts +2 -0
- package/dist/runtime/vue/composables/use-subscription.d.ts +1 -15
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +2 -3
- package/dist/runtime/vue/hydration.d.ts +3 -24
- package/dist/runtime/vue/index.d.ts +2 -2
- package/dist/runtime/vue/index.js +1 -0
- package/dist/runtime/vue/plugin.js +1 -1
- package/dist/runtime/vue/provide.d.ts +5 -5
- package/dist/types.d.mts +3 -0
- package/package.json +103 -56
- package/dist/devtools-client/_nuxt/B29GGJ5n.js +0 -1
- package/dist/devtools-client/_nuxt/BSDEpQet.js +0 -1
- package/dist/devtools-client/_nuxt/BY23OQOo.js +0 -1
- package/dist/devtools-client/_nuxt/BZYRaL-G.js +0 -1
- package/dist/devtools-client/_nuxt/Byw6FxnC.js +0 -1
- package/dist/devtools-client/_nuxt/CJJ_hcZN.js +0 -1
- package/dist/devtools-client/_nuxt/CS6WMFOx.js +0 -2
- package/dist/devtools-client/_nuxt/Cr6_aklh.js +0 -2
- package/dist/devtools-client/_nuxt/DNfpyBVU.js +0 -3
- package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +0 -1
- package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +0 -1
- package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +0 -1
- package/dist/devtools-client/_nuxt/ncgEdbEt.js +0 -1
- package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +0 -1
- package/dist/module.d.ts +0 -189
- package/dist/module.js +0 -489
- package/dist/types.d.ts +0 -3
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { type PaginatedQueryArgs, type PaginatedQueryItem, type PaginatedQueryReference, type PaginationStatus } from '../../vue/composables/use-paginated-query.js';
|
|
3
|
+
import { type AsyncQueryOptions } from './use-async-query.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link useAsyncPaginatedQuery}: `usePaginatedQuery`'s
|
|
6
|
+
* `initialNumItems` plus the SSR options of {@link AsyncQueryOptions}.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface AsyncPaginatedQueryOptions extends Pick<AsyncQueryOptions, 'key' | 'server' | 'lazy' | 'token'> {
|
|
11
|
+
/** How many items the first page holds — on the server and on the client. */
|
|
12
|
+
initialNumItems: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The reactive half of {@link AsyncPaginatedQueryReturn}: `usePaginatedQuery`'s
|
|
16
|
+
* fields plus the `error` and `refresh` of the Nuxt data contract.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export interface AsyncPaginatedQueryData<Item> {
|
|
21
|
+
/**
|
|
22
|
+
* The rows: the server-rendered first page until the live subscription
|
|
23
|
+
* delivers its own, then every loaded page, live. The server page is
|
|
24
|
+
* shown for the initial args only — after an args change the list is
|
|
25
|
+
* empty until the new first page arrives.
|
|
26
|
+
*/
|
|
27
|
+
results: ComputedRef<Item[]>;
|
|
28
|
+
/**
|
|
29
|
+
* `usePaginatedQuery`'s status. Before the live subscription has its first
|
|
30
|
+
* page it is derived from the server page: `'CanLoadMore'` or `'Exhausted'`.
|
|
31
|
+
*/
|
|
32
|
+
status: ComputedRef<PaginationStatus>;
|
|
33
|
+
/** `true` while no page — server or live — is available yet, or more rows are loading. */
|
|
34
|
+
isLoading: ComputedRef<boolean>;
|
|
35
|
+
/** The SSR fetch error or the live subscription's, `null` when none. Never thrown. */
|
|
36
|
+
error: ComputedRef<Error | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch `numItems` more rows. Takes effect once the live subscription has
|
|
39
|
+
* its first page (`status` no longer derived from the server page).
|
|
40
|
+
*/
|
|
41
|
+
loadMore: (numItems: number) => void;
|
|
42
|
+
/** Re-run the server-side first-page fetch (client-side it is a no-op — the subscription is live). */
|
|
43
|
+
refresh: (opts?: {
|
|
44
|
+
dedupe?: 'cancel' | 'defer';
|
|
45
|
+
}) => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Result of {@link useAsyncPaginatedQuery}: awaitable like `useAsyncData`, and
|
|
49
|
+
* the refs of {@link AsyncPaginatedQueryData}.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export interface AsyncPaginatedQueryReturn<Item> extends PromiseLike<AsyncPaginatedQueryData<Item>>, AsyncPaginatedQueryData<Item> {
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A paginated Convex query the Nuxt way: the first page is fetched during SSR
|
|
57
|
+
* and hydrated from the payload, then {@link usePaginatedQuery} takes over on
|
|
58
|
+
* the client — live rows and `loadMore`, no loading flash for the first page.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```vue
|
|
62
|
+
* <script setup lang="ts">
|
|
63
|
+
* import { api } from '#convex/api'
|
|
64
|
+
*
|
|
65
|
+
* const { results, status, loadMore } = useAsyncPaginatedQuery(
|
|
66
|
+
* api.messages.list,
|
|
67
|
+
* {},
|
|
68
|
+
* { initialNumItems: 20 },
|
|
69
|
+
* )
|
|
70
|
+
* </script>
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* The payload entry is keyed on the query name, the *initial* args and
|
|
74
|
+
* `initialNumItems` (override with `options.key`). Client-side navigation does
|
|
75
|
+
* not fetch a page over HTTP: the subscription opens straight away, and the
|
|
76
|
+
* server page only matters for the first paint.
|
|
77
|
+
*
|
|
78
|
+
* @param query - a `FunctionReference` for the public paginated query.
|
|
79
|
+
* @param args - the query's arguments without `paginationOpts`, or `'skip'`.
|
|
80
|
+
* Accepts a ref, computed, or getter.
|
|
81
|
+
* @param options - {@link AsyncPaginatedQueryOptions}.
|
|
82
|
+
* @returns An awaitable {@link AsyncPaginatedQueryReturn}.
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export declare function useAsyncPaginatedQuery<Query extends PaginatedQueryReference>(query: Query, args: MaybeRefOrGetter<PaginatedQueryArgs<Query> | 'skip'>, options: AsyncPaginatedQueryOptions): AsyncPaginatedQueryReturn<PaginatedQueryItem<Query>>;
|
|
87
|
+
/** @public */
|
|
88
|
+
export declare const useConvexAsyncPaginatedQuery: typeof useAsyncPaginatedQuery;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useAsyncData } from "#app";
|
|
2
|
+
import { computed, toValue } from "vue";
|
|
3
|
+
import { getFunctionName } from "convex/server";
|
|
4
|
+
import { convexToJson, jsonToConvex } from "convex/values";
|
|
5
|
+
import {
|
|
6
|
+
usePaginatedQuery
|
|
7
|
+
} from "../../vue/composables/use-paginated-query.js";
|
|
8
|
+
import { fetchQuery } from "../index.js";
|
|
9
|
+
import { useAsyncQueryContext } from "./use-async-query.js";
|
|
10
|
+
export function useAsyncPaginatedQuery(query, args, options) {
|
|
11
|
+
const { initialNumItems, server = true, lazy = false, token } = options;
|
|
12
|
+
const queryName = getFunctionName(query);
|
|
13
|
+
const initialArgs = toValue(args);
|
|
14
|
+
const key = options.key ?? defaultAsyncPaginatedQueryKey(queryName, initialArgs, initialNumItems);
|
|
15
|
+
const { client, deploymentUrl, resolveToken } = useAsyncQueryContext(token);
|
|
16
|
+
const asyncData = useAsyncData(
|
|
17
|
+
key,
|
|
18
|
+
async () => {
|
|
19
|
+
const currentArgs = toValue(args);
|
|
20
|
+
if (currentArgs === "skip" || !import.meta.server) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!deploymentUrl) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"`useAsyncPaginatedQuery` could not fetch during SSR: no Convex deployment URL is configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const result2 = await fetchQuery(
|
|
29
|
+
query,
|
|
30
|
+
{ ...currentArgs, paginationOpts: { numItems: initialNumItems, cursor: null } },
|
|
31
|
+
{ token: await resolveToken(), url: deploymentUrl }
|
|
32
|
+
);
|
|
33
|
+
return { page: convexToJson(result2.page), isDone: result2.isDone };
|
|
34
|
+
},
|
|
35
|
+
{ server, lazy, deep: false, immediate: initialArgs !== "skip" }
|
|
36
|
+
);
|
|
37
|
+
const payloadPage = computed(() => {
|
|
38
|
+
const entry = asyncData.data.value;
|
|
39
|
+
return entry ? jsonToConvex(entry.page) : void 0;
|
|
40
|
+
});
|
|
41
|
+
const live = !import.meta.server && client ? usePaginatedQuery(query, args, { initialNumItems }) : void 0;
|
|
42
|
+
const liveState = computed(() => {
|
|
43
|
+
if (!live) return void 0;
|
|
44
|
+
try {
|
|
45
|
+
return { results: live.results.value, status: live.status.value, error: null };
|
|
46
|
+
} catch (thrown) {
|
|
47
|
+
return { results: [], status: "LoadingFirstPage", error: thrown };
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const liveHasPage = computed(() => liveState.value !== void 0 && liveState.value.status !== "LoadingFirstPage");
|
|
51
|
+
const clientError = computed(() => {
|
|
52
|
+
if (import.meta.server || client || toValue(args) === "skip") return null;
|
|
53
|
+
return new Error(
|
|
54
|
+
"`useAsyncPaginatedQuery` could not start on the client: no Convex client is available. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config."
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
const initialArgsJson = argsJson(initialArgs);
|
|
58
|
+
let payloadRetired = false;
|
|
59
|
+
const payloadApplies = computed(() => {
|
|
60
|
+
if (liveHasPage.value || argsJson(toValue(args)) !== initialArgsJson) payloadRetired = true;
|
|
61
|
+
return !payloadRetired && payloadPage.value !== void 0;
|
|
62
|
+
});
|
|
63
|
+
const results = computed(() => {
|
|
64
|
+
if (liveHasPage.value) return liveState.value.results;
|
|
65
|
+
return payloadApplies.value ? payloadPage.value : [];
|
|
66
|
+
});
|
|
67
|
+
const status = computed(() => {
|
|
68
|
+
if (liveHasPage.value) return liveState.value.status;
|
|
69
|
+
if (payloadApplies.value) return asyncData.data.value.isDone ? "Exhausted" : "CanLoadMore";
|
|
70
|
+
return "LoadingFirstPage";
|
|
71
|
+
});
|
|
72
|
+
const isLoading = computed(() => status.value === "LoadingFirstPage" || status.value === "LoadingMore");
|
|
73
|
+
const error = computed(
|
|
74
|
+
() => liveState.value?.error ?? clientError.value ?? asyncData.error.value ?? null
|
|
75
|
+
);
|
|
76
|
+
const loadMore = (numItems) => {
|
|
77
|
+
live?.loadMore(numItems);
|
|
78
|
+
};
|
|
79
|
+
const refresh = (opts) => asyncData.refresh(opts);
|
|
80
|
+
const result = { results, status, isLoading, error, loadMore, refresh };
|
|
81
|
+
const promise = Promise.resolve(asyncData).then(() => result);
|
|
82
|
+
return Object.assign(promise, result);
|
|
83
|
+
}
|
|
84
|
+
function argsJson(args) {
|
|
85
|
+
return args === "skip" ? '"skip"' : JSON.stringify(convexToJson(args ?? {}));
|
|
86
|
+
}
|
|
87
|
+
export function defaultAsyncPaginatedQueryKey(queryName, initialArgs, initialNumItems) {
|
|
88
|
+
return `convex:paginated:${queryName}:${argsJson(initialArgs)}:${initialNumItems}`;
|
|
89
|
+
}
|
|
90
|
+
export const useConvexAsyncPaginatedQuery = useAsyncPaginatedQuery;
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nuxt-idiomatic data fetching for Convex queries.
|
|
3
|
-
*
|
|
4
|
-
* {@link useAsyncQuery} is a Vue/Nuxt-only addition (no `convex/react`
|
|
5
|
-
* counterpart — see PARITY.md): it marries Nuxt's `useAsyncData` model with
|
|
6
|
-
* Convex's live queries. During SSR the query runs over HTTP and the result is
|
|
7
|
-
* embedded in the Nuxt payload; after hydration the composable upgrades to the
|
|
8
|
-
* WebSocket subscription, so the page paints with server data and stays live —
|
|
9
|
-
* no loading flash, no client refetch.
|
|
10
|
-
*
|
|
11
|
-
* @module
|
|
12
|
-
*/
|
|
13
1
|
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
14
2
|
import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
|
|
15
3
|
/**
|
|
@@ -94,7 +82,13 @@ export interface AsyncQueryReturn<T> extends PromiseLike<AsyncQueryData<T>> {
|
|
|
94
82
|
dedupe?: 'cancel' | 'defer';
|
|
95
83
|
}) => Promise<void>;
|
|
96
84
|
}
|
|
97
|
-
|
|
85
|
+
/**
|
|
86
|
+
* The reactive half of {@link AsyncQueryReturn} — what `await useAsyncQuery(...)`
|
|
87
|
+
* resolves to, and the shape to annotate a value passed on from the call site.
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
export type AsyncQueryData<T> = Pick<AsyncQueryReturn<T>, 'data' | 'error' | 'status' | 'refresh'>;
|
|
98
92
|
/**
|
|
99
93
|
* Load a Convex query the Nuxt way: fetched on the server during SSR,
|
|
100
94
|
* hydrated through the Nuxt payload, then seamlessly upgraded to a live
|
|
@@ -137,8 +131,5 @@ type AsyncQueryData<T> = Pick<AsyncQueryReturn<T>, 'data' | 'error' | 'status' |
|
|
|
137
131
|
* @public
|
|
138
132
|
*/
|
|
139
133
|
export declare function useAsyncQuery<Query extends FunctionReference<'query'>>(query: Query, args?: MaybeRefOrGetter<FunctionArgs<Query> | 'skip'>, options?: AsyncQueryOptions): AsyncQueryReturn<FunctionReturnType<Query>>;
|
|
140
|
-
/** Payload key for a query + initial args. Exported for tests. @internal */
|
|
141
|
-
export declare function defaultAsyncQueryKey(queryName: string, initialArgs: unknown): string;
|
|
142
134
|
/** @public */
|
|
143
135
|
export declare const useConvexAsyncQuery: typeof useAsyncQuery;
|
|
144
|
-
export {};
|
|
@@ -4,6 +4,7 @@ import { getFunctionName, makeFunctionReference } from "convex/server";
|
|
|
4
4
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
5
5
|
import { useConvex } from "../../vue/client.js";
|
|
6
6
|
import { useConvexQueries } from "../../vue/composables/use-queries.js";
|
|
7
|
+
import { CONVEX_INITIAL_TOKEN_KEY } from "../config.js";
|
|
7
8
|
import { fetchQuery } from "../index.js";
|
|
8
9
|
export function useAsyncQuery(query, args, options = {}) {
|
|
9
10
|
const { server = true, lazy = false, live = true, token } = options;
|
|
@@ -11,13 +12,7 @@ export function useAsyncQuery(query, args, options = {}) {
|
|
|
11
12
|
const queryName = getFunctionName(queryReference);
|
|
12
13
|
const initialArgs = toValue(args) ?? {};
|
|
13
14
|
const key = options.key ?? defaultAsyncQueryKey(queryName, initialArgs);
|
|
14
|
-
const
|
|
15
|
-
const client = useConvex();
|
|
16
|
-
const deploymentUrl = import.meta.server ? useRuntimeConfig().public.convex.url : "";
|
|
17
|
-
const resolveToken = async () => {
|
|
18
|
-
const resolved = typeof token === "function" ? await token() : token ?? initialToken.value;
|
|
19
|
-
return resolved ?? void 0;
|
|
20
|
-
};
|
|
15
|
+
const { client, deploymentUrl, resolveToken } = useAsyncQueryContext(token);
|
|
21
16
|
const asyncData = useAsyncData(
|
|
22
17
|
key,
|
|
23
18
|
async () => {
|
|
@@ -28,7 +23,7 @@ export function useAsyncQuery(query, args, options = {}) {
|
|
|
28
23
|
if (import.meta.server) {
|
|
29
24
|
if (!deploymentUrl) {
|
|
30
25
|
throw new Error(
|
|
31
|
-
"`useAsyncQuery` could not fetch during SSR: no Convex deployment URL is configured. Set
|
|
26
|
+
"`useAsyncQuery` could not fetch during SSR: no Convex deployment URL is configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config."
|
|
32
27
|
);
|
|
33
28
|
}
|
|
34
29
|
const value2 = await fetchQuery(queryReference, currentArgs, {
|
|
@@ -39,7 +34,7 @@ export function useAsyncQuery(query, args, options = {}) {
|
|
|
39
34
|
}
|
|
40
35
|
if (!client) {
|
|
41
36
|
throw new Error(
|
|
42
|
-
"`useAsyncQuery` could not fetch on the client: no Convex client is available.
|
|
37
|
+
"`useAsyncQuery` could not fetch on the client: no Convex client is available. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config."
|
|
43
38
|
);
|
|
44
39
|
}
|
|
45
40
|
const value = await client.query(queryReference, currentArgs);
|
|
@@ -97,6 +92,16 @@ export function useAsyncQuery(query, args, options = {}) {
|
|
|
97
92
|
const promise = Promise.resolve(asyncData).then(() => result);
|
|
98
93
|
return Object.assign(promise, result);
|
|
99
94
|
}
|
|
95
|
+
export function useAsyncQueryContext(token) {
|
|
96
|
+
const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
|
|
97
|
+
const client = useConvex();
|
|
98
|
+
const deploymentUrl = import.meta.server ? useRuntimeConfig().public.convex.url : "";
|
|
99
|
+
const resolveToken = async () => {
|
|
100
|
+
const resolved = typeof token === "function" ? await token() : token ?? initialToken.value;
|
|
101
|
+
return resolved ?? void 0;
|
|
102
|
+
};
|
|
103
|
+
return { client, deploymentUrl, resolveToken };
|
|
104
|
+
}
|
|
100
105
|
export function defaultAsyncQueryKey(queryName, initialArgs) {
|
|
101
106
|
const argsJson = initialArgs === "skip" ? '"skip"' : JSON.stringify(convexToJson(initialArgs ?? {}));
|
|
102
107
|
return `convex:${queryName}:${argsJson}`;
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuxt `useState` key the Better Auth server plugin stashes the SSR-prefetched
|
|
3
|
+
* Convex JWT under, read back by the client plugin and `useAsyncQuery`. Lives
|
|
4
|
+
* here, dependency-free, so the core composable can read the token without
|
|
5
|
+
* importing the integration.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONVEX_INITIAL_TOKEN_KEY = "convex:initialToken";
|
|
8
|
+
/**
|
|
9
|
+
* The module's runtime config, tolerating a missing Nuxt context (a plain
|
|
10
|
+
* server import outside a request) by returning an empty object.
|
|
11
|
+
*/
|
|
1
12
|
export declare function getConvexRuntimeConfig(): {
|
|
2
13
|
url: string | undefined;
|
|
3
14
|
siteUrl: string | undefined;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind the Convex-aware Content Security Policy the module
|
|
3
|
+
* applies when `nuxt-security` is present — shared by the Nitro plugin
|
|
4
|
+
* (`./security`) and the unit tests. Import-free so it runs anywhere.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The slice of a nuxt-security rule set this module touches: the resolved
|
|
8
|
+
* options for one route (`rules['/**']` at runtime), where `headers` and
|
|
9
|
+
* `contentSecurityPolicy` may each be `false` when the app switched them off.
|
|
10
|
+
*/
|
|
11
|
+
export interface SecurityRules {
|
|
12
|
+
headers?: false | {
|
|
13
|
+
contentSecurityPolicy?: false | Record<string, unknown>;
|
|
14
|
+
[header: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
[option: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* CSP `connect-src` entries the browser needs to reach a Convex deployment: the
|
|
20
|
+
* deployment URL over both HTTPS and WebSocket (the realtime sync channel) and,
|
|
21
|
+
* when configured, the `.site` URL that serves Convex HTTP actions. Returns
|
|
22
|
+
* `[]` for empty or unparseable input.
|
|
23
|
+
*/
|
|
24
|
+
export declare function convexConnectSrc(url?: string, siteUrl?: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* CSP sources for Convex-served resources (`img-src` / `media-src`): files
|
|
27
|
+
* uploaded through `useStorageUrl()` are served from the deployment origin,
|
|
28
|
+
* and files streamed by HTTP actions from the `.site` origin.
|
|
29
|
+
*/
|
|
30
|
+
export declare function convexResourceSrc(url?: string, siteUrl?: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Apply nuxt-convex-module's secure-by-default, Convex-aware CSP onto a
|
|
33
|
+
* nuxt-security rule set (mutated in place). This tightens the directives we
|
|
34
|
+
* can safely pre-fill — locking network egress and Convex-served media to
|
|
35
|
+
* same-origin plus the Convex deployment — while leaving every other directive
|
|
36
|
+
* (script/style/font, etc.) to nuxt-security's own defaults.
|
|
37
|
+
*
|
|
38
|
+
* For each directive we keep any value the user already set and *append* the
|
|
39
|
+
* Convex origins, so consumers extend (add their own third parties) rather than
|
|
40
|
+
* fight the defaults, and the Convex origins are always present. A user who sets
|
|
41
|
+
* `contentSecurityPolicy: false` (CSP disabled) is left untouched, as is a rule
|
|
42
|
+
* set with nothing to add (no Convex URL).
|
|
43
|
+
*/
|
|
44
|
+
export declare function applyConvexSecurityDefaults(rules: SecurityRules, connectSrc: string[], resourceSrc: string[]): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function toHttpOrigin(raw) {
|
|
2
|
+
if (!raw) return void 0;
|
|
3
|
+
try {
|
|
4
|
+
return new URL(raw).origin;
|
|
5
|
+
} catch {
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function toWsOrigin(raw) {
|
|
10
|
+
if (!raw) return void 0;
|
|
11
|
+
try {
|
|
12
|
+
const u = new URL(raw);
|
|
13
|
+
return `${u.protocol === "https:" ? "wss:" : "ws:"}//${u.host}`;
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function uniq(values) {
|
|
19
|
+
return Array.from(new Set(values.filter((v) => Boolean(v))));
|
|
20
|
+
}
|
|
21
|
+
export function convexConnectSrc(url, siteUrl) {
|
|
22
|
+
return uniq([toHttpOrigin(url), toWsOrigin(url), toHttpOrigin(siteUrl), toWsOrigin(siteUrl)]);
|
|
23
|
+
}
|
|
24
|
+
export function convexResourceSrc(url, siteUrl) {
|
|
25
|
+
return uniq([toHttpOrigin(url), toHttpOrigin(siteUrl)]);
|
|
26
|
+
}
|
|
27
|
+
export function applyConvexSecurityDefaults(rules, connectSrc, resourceSrc) {
|
|
28
|
+
if (connectSrc.length === 0 && resourceSrc.length === 0) return;
|
|
29
|
+
if (rules.headers === false) return;
|
|
30
|
+
if (typeof rules.headers !== "object" || rules.headers === null) rules.headers = {};
|
|
31
|
+
const headers = rules.headers;
|
|
32
|
+
if (headers.contentSecurityPolicy === false) return;
|
|
33
|
+
if (typeof headers.contentSecurityPolicy !== "object" || headers.contentSecurityPolicy === null) {
|
|
34
|
+
headers.contentSecurityPolicy = {};
|
|
35
|
+
}
|
|
36
|
+
const csp = headers.contentSecurityPolicy;
|
|
37
|
+
tightenDirective(csp, "connect-src", ["'self'"], connectSrc);
|
|
38
|
+
tightenDirective(csp, "img-src", ["'self'", "data:"], resourceSrc);
|
|
39
|
+
tightenDirective(csp, "media-src", ["'self'"], resourceSrc);
|
|
40
|
+
}
|
|
41
|
+
function tightenDirective(csp, name, baseline, additions) {
|
|
42
|
+
if (additions.length === 0) return;
|
|
43
|
+
const current = csp[name];
|
|
44
|
+
const existing = Array.isArray(current) ? current : typeof current === "string" ? current.split(/\s+/).filter(Boolean) : baseline;
|
|
45
|
+
csp[name] = uniq([...existing, ...additions]);
|
|
46
|
+
}
|
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ## Usage
|
|
10
10
|
*
|
|
11
|
-
* All exported functions assume that a Convex deployment URL is
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
11
|
+
* All exported functions assume that a Convex deployment URL is configured —
|
|
12
|
+
* `convex.url` in `nuxt.config`, `NUXT_PUBLIC_CONVEX_URL`, or the `CONVEX_URL`
|
|
13
|
+
* that `npx convex dev` writes to `.env.local`. Unlike its Next counterpart,
|
|
14
|
+
* `npx convex dev` does *not* set the framework-prefixed name for Nuxt: the
|
|
15
|
+
* Convex CLI has no Nuxt case in its framework detection, so it writes the
|
|
16
|
+
* unprefixed `CONVEX_URL`, which the module reads too.
|
|
14
17
|
*
|
|
15
18
|
* ### Preloading data
|
|
16
19
|
*
|
|
@@ -51,15 +54,12 @@ export type NuxtOptions = {
|
|
|
51
54
|
token?: string;
|
|
52
55
|
/**
|
|
53
56
|
* The URL of the Convex deployment to use for the function call.
|
|
54
|
-
* Defaults to `
|
|
57
|
+
* Defaults to `runtimeConfig.public.convex.url`, then to
|
|
58
|
+
* `process.env.NUXT_PUBLIC_CONVEX_URL`, if not provided.
|
|
55
59
|
*
|
|
56
60
|
* Explicitly passing undefined here (such as from missing ENV variables) will throw an error in the future.
|
|
57
61
|
*/
|
|
58
62
|
url?: string;
|
|
59
|
-
/**
|
|
60
|
-
* @internal
|
|
61
|
-
*/
|
|
62
|
-
adminToken?: string;
|
|
63
63
|
/**
|
|
64
64
|
* Skip validating that the Convex deployment URL looks like
|
|
65
65
|
* `https://happy-animal-123.convex.cloud` or localhost.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SecurityRules } from './csp.js';
|
|
2
|
+
/**
|
|
3
|
+
* The slice of the Nitro app this plugin touches, typed locally: the hook's
|
|
4
|
+
* real declaration lives in the types nuxt-security generates into the app,
|
|
5
|
+
* which only exist when the app has it installed — and this plugin is only
|
|
6
|
+
* registered then. Nitro itself is not imported, so the published types don't
|
|
7
|
+
* depend on how the app resolves `nitropack`.
|
|
8
|
+
*/
|
|
9
|
+
export interface SecurityNitroApp {
|
|
10
|
+
hooks: {
|
|
11
|
+
hook: (name: 'nuxt-security:routeRules', fn: (rules: Record<string, SecurityRules>) => void) => unknown;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Nitro plugin: extend nuxt-security's Content Security Policy with the Convex
|
|
16
|
+
* origins. Registered by the module only when the nuxt-security integration is
|
|
17
|
+
* on (see `registerSecurity` in `module.ts`).
|
|
18
|
+
*
|
|
19
|
+
* Listens for `nuxt-security:routeRules`, which nuxt-security fires once its
|
|
20
|
+
* global config and every route rule are merged into one table — so the
|
|
21
|
+
* origins are appended to whatever the app configured, whatever the order of
|
|
22
|
+
* `modules`. nuxt-security sorts its own Nitro plugins last, so this listener
|
|
23
|
+
* is in place before the hook fires. The deployment URL comes from runtime
|
|
24
|
+
* config, so it need not be known at build time.
|
|
25
|
+
*
|
|
26
|
+
* `connect-src` is tightened only in production: creating it in dev would
|
|
27
|
+
* block the Vite HMR and devtools WebSockets, which run on ports `'self'`
|
|
28
|
+
* doesn't cover, while nuxt-security's defaults leave the directive open.
|
|
29
|
+
* `img-src` / `media-src` are widened in dev too — nuxt-security enforces its
|
|
30
|
+
* default `img-src 'self' data:` during `nuxt dev` as well, which would block
|
|
31
|
+
* files served from Convex storage.
|
|
32
|
+
*/
|
|
33
|
+
export default function convexSecurityPlugin(nitroApp: SecurityNitroApp): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getConvexRuntimeConfig } from "./config.js";
|
|
2
|
+
import { applyConvexSecurityDefaults, convexConnectSrc, convexResourceSrc } from "./csp.js";
|
|
3
|
+
export default function convexSecurityPlugin(nitroApp) {
|
|
4
|
+
nitroApp.hooks.hook("nuxt-security:routeRules", (rules) => {
|
|
5
|
+
const global = rules["/**"];
|
|
6
|
+
if (!global) return;
|
|
7
|
+
const { url, siteUrl } = getConvexRuntimeConfig();
|
|
8
|
+
applyConvexSecurityDefaults(
|
|
9
|
+
global,
|
|
10
|
+
import.meta.dev ? [] : convexConnectSrc(url, siteUrl),
|
|
11
|
+
convexResourceSrc(url, siteUrl)
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -48,10 +48,15 @@ export interface PolarComponentApi {
|
|
|
48
48
|
}>;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
+
* Renders a link to the Polar customer portal for the signed-in user, where
|
|
52
|
+
* they manage their subscription.
|
|
53
|
+
*
|
|
51
54
|
* @example
|
|
52
55
|
* ```vue
|
|
53
56
|
* <CustomerPortalLink>Manage subscription</CustomerPortalLink>
|
|
54
57
|
* ```
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
55
60
|
*/
|
|
56
61
|
export declare const CustomerPortalLink: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
57
62
|
polarApi: {
|
|
@@ -86,6 +91,8 @@ export declare const CustomerPortalLink: import("vue").DefineComponent<import("v
|
|
|
86
91
|
* Start free trial
|
|
87
92
|
* </CheckoutLink>
|
|
88
93
|
* ```
|
|
94
|
+
*
|
|
95
|
+
* @public
|
|
89
96
|
*/
|
|
90
97
|
export declare const CheckoutLink: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
91
98
|
polarApi: {
|
|
@@ -172,9 +179,9 @@ export declare const CheckoutLink: import("vue").DefineComponent<import("vue").E
|
|
|
172
179
|
default: boolean;
|
|
173
180
|
};
|
|
174
181
|
}>> & Readonly<{}>, {
|
|
182
|
+
embed: boolean;
|
|
175
183
|
lazy: boolean;
|
|
176
184
|
metadata: Record<string, string>;
|
|
177
|
-
embed: boolean;
|
|
178
185
|
subscriptionId: string;
|
|
179
186
|
trialInterval: "day" | "week" | "month" | "year" | null;
|
|
180
187
|
trialIntervalCount: number | null;
|
|
@@ -13,7 +13,7 @@ export const CustomerPortalLink = defineComponent({
|
|
|
13
13
|
const polarApi = props.polarApi ?? useConvexNamespace("billing");
|
|
14
14
|
const generateCustomerPortalUrl = polarApi?.generateCustomerPortalUrl ? useAction(polarApi.generateCustomerPortalUrl) : void 0;
|
|
15
15
|
const portalUrl = ref();
|
|
16
|
-
if (import.meta.
|
|
16
|
+
if (!import.meta.server && generateCustomerPortalUrl) {
|
|
17
17
|
watch(
|
|
18
18
|
() => props.returnUrl,
|
|
19
19
|
(returnUrl) => {
|
|
@@ -55,7 +55,7 @@ export const CheckoutLink = defineComponent({
|
|
|
55
55
|
const generateCheckoutLink = polarApi?.generateCheckoutLink ? useAction(polarApi.generateCheckoutLink) : void 0;
|
|
56
56
|
const checkoutLink = ref();
|
|
57
57
|
const isLoading = ref(false);
|
|
58
|
-
if (import.meta.
|
|
58
|
+
if (!import.meta.server && generateCheckoutLink) {
|
|
59
59
|
watch(
|
|
60
60
|
() => [props.lazy, props.productIds, props.subscriptionId, props.metadata, props.embed, props.trialInterval, props.trialIntervalCount, props.locale],
|
|
61
61
|
async () => {
|
|
@@ -99,7 +99,7 @@ export const CheckoutLink = defineComponent({
|
|
|
99
99
|
const { PolarEmbedCheckout } = await import("@polar-sh/checkout/embed");
|
|
100
100
|
await PolarEmbedCheckout.create(url, { theme: props.theme });
|
|
101
101
|
} else {
|
|
102
|
-
window.open(url, "_blank");
|
|
102
|
+
window.open(url, "_blank", "noopener");
|
|
103
103
|
}
|
|
104
104
|
} finally {
|
|
105
105
|
isLoading.value = false;
|
|
@@ -89,9 +89,9 @@ export interface ConvexAuthProviderOptions {
|
|
|
89
89
|
* @example
|
|
90
90
|
* ```vue
|
|
91
91
|
* <script setup lang="ts">
|
|
92
|
-
* import { useAuth } from '~/composables/
|
|
92
|
+
* import { useAuth } from '~/composables/useMyAuthProvider' // your auth provider
|
|
93
93
|
*
|
|
94
|
-
* const
|
|
94
|
+
* const client = useConvex()
|
|
95
95
|
* const authState = provideConvexAuth({ client, useAuth })
|
|
96
96
|
* </script>
|
|
97
97
|
* ```
|
|
@@ -112,11 +112,11 @@ export declare function provideConvexAuth(options: ConvexAuthProviderOptions): C
|
|
|
112
112
|
* (`nuxtApp.vueApp.provide`) rather than from a component's setup function.
|
|
113
113
|
*
|
|
114
114
|
* When `scope` is provided, all watchers are created inside that
|
|
115
|
-
*
|
|
115
|
+
* `EffectScope` so callers can dispose them later.
|
|
116
116
|
*/
|
|
117
117
|
export declare function createConvexAuthState(options: ConvexAuthProviderOptions, scope?: EffectScope): ConvexAuthState;
|
|
118
118
|
/**
|
|
119
|
-
* Create a fresh
|
|
119
|
+
* Create a fresh `EffectScope` and build a Convex auth state inside it.
|
|
120
120
|
* The scope is returned so the caller can `.stop()` it on teardown.
|
|
121
121
|
*/
|
|
122
122
|
export declare function createScopedConvexAuthState(options: ConvexAuthProviderOptions): {
|
|
@@ -84,10 +84,14 @@ export function createConvexAuthState(options, scope) {
|
|
|
84
84
|
const isAuthenticated = computed(
|
|
85
85
|
() => toValue(authProviderAuthenticated) && (isConvexAuthenticated.value ?? false)
|
|
86
86
|
);
|
|
87
|
+
const isLoading = computed(() => isConvexAuthenticated.value === null);
|
|
88
|
+
const isRefreshingWhileAuthenticated = computed(
|
|
89
|
+
() => isRefreshing.value && isAuthenticated.value
|
|
90
|
+
);
|
|
87
91
|
return {
|
|
88
|
-
isLoading
|
|
92
|
+
isLoading,
|
|
89
93
|
isAuthenticated,
|
|
90
|
-
isRefreshing:
|
|
94
|
+
isRefreshing: isRefreshingWhileAuthenticated
|
|
91
95
|
};
|
|
92
96
|
}
|
|
93
97
|
export function createScopedConvexAuthState(options) {
|