nuxt-convex-module 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/chunks/index.mjs +64 -0
- package/dist/devtools-client/200.html +1 -0
- package/dist/devtools-client/404.html +1 -0
- package/dist/devtools-client/_nuxt/B29GGJ5n.js +1 -0
- package/dist/devtools-client/_nuxt/BSDEpQet.js +1 -0
- package/dist/devtools-client/_nuxt/BY23OQOo.js +1 -0
- package/dist/devtools-client/_nuxt/BZYRaL-G.js +1 -0
- package/dist/devtools-client/_nuxt/Byw6FxnC.js +1 -0
- package/dist/devtools-client/_nuxt/CJJ_hcZN.js +1 -0
- package/dist/devtools-client/_nuxt/CS6WMFOx.js +2 -0
- package/dist/devtools-client/_nuxt/CZbAGcNC.js +1 -0
- package/dist/devtools-client/_nuxt/Cr6_aklh.js +2 -0
- package/dist/devtools-client/_nuxt/DNfpyBVU.js +3 -0
- package/dist/devtools-client/_nuxt/DPWXUmcI.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
- package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +1 -0
- package/dist/devtools-client/_nuxt/dy_xXcv9.js +1 -0
- package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +1 -0
- package/dist/devtools-client/_nuxt/ncgEdbEt.js +1 -0
- package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +1 -0
- package/dist/devtools-client/auth/index.html +1 -0
- package/dist/devtools-client/icon.svg +6 -0
- package/dist/devtools-client/index.html +1 -0
- package/dist/devtools-client/logs/index.html +1 -0
- package/dist/devtools-client/queries/index.html +1 -0
- package/dist/module.d.ts +189 -0
- package/dist/module.js +489 -0
- package/dist/module.json +13 -0
- package/dist/runtime/auth0/vue/index.d.ts +58 -0
- package/dist/runtime/auth0/vue/index.js +33 -0
- package/dist/runtime/better-auth/nuxt/middleware.d.ts +14 -0
- package/dist/runtime/better-auth/nuxt/middleware.js +39 -0
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +6 -0
- package/dist/runtime/better-auth/nuxt/proxy.js +11 -0
- package/dist/runtime/better-auth/nuxt/server.d.ts +106 -0
- package/dist/runtime/better-auth/nuxt/server.js +123 -0
- package/dist/runtime/better-auth/vue/auth-boundary.d.ts +131 -0
- package/dist/runtime/better-auth/vue/auth-boundary.js +78 -0
- package/dist/runtime/better-auth/vue/client.d.ts +358 -0
- package/dist/runtime/better-auth/vue/client.js +5 -0
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +39 -0
- package/dist/runtime/better-auth/vue/cross-domain.js +45 -0
- package/dist/runtime/better-auth/vue/hydration.d.ts +20 -0
- package/dist/runtime/better-auth/vue/hydration.js +52 -0
- package/dist/runtime/better-auth/vue/index.d.ts +12 -0
- package/dist/runtime/better-auth/vue/index.js +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.d.ts +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.js +32 -0
- package/dist/runtime/better-auth/vue/plugin.server.d.ts +24 -0
- package/dist/runtime/better-auth/vue/plugin.server.js +42 -0
- package/dist/runtime/better-auth/vue/use-auth.d.ts +92 -0
- package/dist/runtime/better-auth/vue/use-auth.js +82 -0
- package/dist/runtime/clerk/vue/index.d.ts +81 -0
- package/dist/runtime/clerk/vue/index.js +49 -0
- package/dist/runtime/devtools/bridge.d.ts +12 -0
- package/dist/runtime/devtools/bridge.js +191 -0
- package/dist/runtime/devtools/plugin.client.d.ts +8 -0
- package/dist/runtime/devtools/plugin.client.js +34 -0
- package/dist/runtime/devtools/types.d.ts +78 -0
- package/dist/runtime/devtools/types.js +0 -0
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +144 -0
- package/dist/runtime/nuxt/composables/use-async-query.js +104 -0
- package/dist/runtime/nuxt/config.d.ts +7 -0
- package/dist/runtime/nuxt/config.js +12 -0
- package/dist/runtime/nuxt/index.d.ts +136 -0
- package/dist/runtime/nuxt/index.js +91 -0
- package/dist/runtime/polar/vue/components.d.ts +184 -0
- package/dist/runtime/polar/vue/components.js +115 -0
- package/dist/runtime/vue/auth/helpers.d.ts +67 -0
- package/dist/runtime/vue/auth/helpers.js +50 -0
- package/dist/runtime/vue/auth/index.d.ts +125 -0
- package/dist/runtime/vue/auth/index.js +97 -0
- package/dist/runtime/vue/client.d.ts +305 -0
- package/dist/runtime/vue/client.js +382 -0
- package/dist/runtime/vue/composables/use-action.d.ts +62 -0
- package/dist/runtime/vue/composables/use-action.js +13 -0
- package/dist/runtime/vue/composables/use-connection-state.d.ts +30 -0
- package/dist/runtime/vue/composables/use-connection-state.js +14 -0
- package/dist/runtime/vue/composables/use-mutation.d.ts +76 -0
- package/dist/runtime/vue/composables/use-mutation.js +34 -0
- package/dist/runtime/vue/composables/use-paginated-query.d.ts +132 -0
- package/dist/runtime/vue/composables/use-paginated-query.js +513 -0
- package/dist/runtime/vue/composables/use-queries.d.ts +62 -0
- package/dist/runtime/vue/composables/use-queries.js +37 -0
- package/dist/runtime/vue/composables/use-query.d.ts +87 -0
- package/dist/runtime/vue/composables/use-query.js +53 -0
- package/dist/runtime/vue/composables/use-storage-url.d.ts +46 -0
- package/dist/runtime/vue/composables/use-storage-url.js +9 -0
- package/dist/runtime/vue/composables/use-subscription.d.ts +15 -0
- package/dist/runtime/vue/composables/use-subscription.js +33 -0
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +117 -0
- package/dist/runtime/vue/composables/use-upload-queue.js +115 -0
- package/dist/runtime/vue/composables/use-upload.d.ts +134 -0
- package/dist/runtime/vue/composables/use-upload.js +114 -0
- package/dist/runtime/vue/hydration.d.ts +66 -0
- package/dist/runtime/vue/hydration.js +46 -0
- package/dist/runtime/vue/index.d.ts +94 -0
- package/dist/runtime/vue/index.js +42 -0
- package/dist/runtime/vue/plugin.d.ts +18 -0
- package/dist/runtime/vue/plugin.js +16 -0
- package/dist/runtime/vue/provide.d.ts +39 -0
- package/dist/runtime/vue/provide.js +15 -0
- package/dist/runtime/vue/queries-observer.d.ts +41 -0
- package/dist/runtime/vue/queries-observer.js +126 -0
- package/dist/types.d.ts +3 -0
- package/package.json +233 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AuthTokenFetcher } from 'convex/browser';
|
|
2
|
+
import { type ComputedRef } from 'vue';
|
|
3
|
+
import { type AuthClient } from '#convex/auth-client';
|
|
4
|
+
declare const useClientSession: () => Readonly<import("vue").Ref<{
|
|
5
|
+
readonly data: {
|
|
6
|
+
readonly user: {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly createdAt: Date;
|
|
9
|
+
readonly updatedAt: Date;
|
|
10
|
+
readonly email: string;
|
|
11
|
+
readonly emailVerified: boolean;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly image?: string | null | undefined;
|
|
14
|
+
readonly userId?: string | null | undefined;
|
|
15
|
+
};
|
|
16
|
+
readonly session: {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly createdAt: Date;
|
|
19
|
+
readonly updatedAt: Date;
|
|
20
|
+
readonly userId: string;
|
|
21
|
+
readonly expiresAt: Date;
|
|
22
|
+
readonly token: string;
|
|
23
|
+
readonly ipAddress?: string | null | undefined;
|
|
24
|
+
readonly userAgent?: string | null | undefined;
|
|
25
|
+
};
|
|
26
|
+
} | null;
|
|
27
|
+
readonly isPending: boolean;
|
|
28
|
+
readonly isRefetching: boolean;
|
|
29
|
+
readonly error: import("better-auth/client").BetterFetchError | null;
|
|
30
|
+
readonly refetch: (queryParams?: {
|
|
31
|
+
query?: import("better-auth").SessionQueryParams;
|
|
32
|
+
} | undefined) => Promise<void>;
|
|
33
|
+
}, {
|
|
34
|
+
readonly data: {
|
|
35
|
+
readonly user: {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly createdAt: Date;
|
|
38
|
+
readonly updatedAt: Date;
|
|
39
|
+
readonly email: string;
|
|
40
|
+
readonly emailVerified: boolean;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly image?: string | null | undefined;
|
|
43
|
+
readonly userId?: string | null | undefined;
|
|
44
|
+
};
|
|
45
|
+
readonly session: {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly createdAt: Date;
|
|
48
|
+
readonly updatedAt: Date;
|
|
49
|
+
readonly userId: string;
|
|
50
|
+
readonly expiresAt: Date;
|
|
51
|
+
readonly token: string;
|
|
52
|
+
readonly ipAddress?: string | null | undefined;
|
|
53
|
+
readonly userAgent?: string | null | undefined;
|
|
54
|
+
};
|
|
55
|
+
} | null;
|
|
56
|
+
readonly isPending: boolean;
|
|
57
|
+
readonly isRefetching: boolean;
|
|
58
|
+
readonly error: import("better-auth/client").BetterFetchError | null;
|
|
59
|
+
readonly refetch: (queryParams?: {
|
|
60
|
+
query?: import("better-auth").SessionQueryParams;
|
|
61
|
+
} | undefined) => Promise<void>;
|
|
62
|
+
}>>;
|
|
63
|
+
export type AuthSession = ReturnType<typeof useClientSession>;
|
|
64
|
+
/** The signed-in user (loose — exact fields depend on your auth schema). */
|
|
65
|
+
export type AuthUser = {
|
|
66
|
+
id: string;
|
|
67
|
+
email: string;
|
|
68
|
+
name: string;
|
|
69
|
+
} & Record<string, unknown>;
|
|
70
|
+
export interface UseAuthService {
|
|
71
|
+
isLoading: ComputedRef<boolean>;
|
|
72
|
+
isAuthenticated: ComputedRef<boolean>;
|
|
73
|
+
fetchAccessToken: AuthTokenFetcher;
|
|
74
|
+
client: AuthClient;
|
|
75
|
+
session: AuthSession;
|
|
76
|
+
/** The current user, or `null` when signed out / still loading. */
|
|
77
|
+
user: ComputedRef<AuthUser | null>;
|
|
78
|
+
authVersion: ComputedRef<string | null>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Unified Better Auth service for the Vue/Nuxt runtime.
|
|
82
|
+
*
|
|
83
|
+
* Returns the full Better Auth client, the reactive session wrapper, and the
|
|
84
|
+
* Convex-compatible auth state used by the packaged auth plugin.
|
|
85
|
+
*
|
|
86
|
+
* @param initialToken - Optional preloaded token, used once per app lifetime
|
|
87
|
+
* to avoid a round-trip on initial load (e.g. from SSR).
|
|
88
|
+
*/
|
|
89
|
+
export declare function useAuth(initialToken?: string | null): UseAuthService;
|
|
90
|
+
/** Reset module-level token cache — intended for tests only. */
|
|
91
|
+
export declare function __resetUseAuthForTests(): void;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { authClient } from "#convex/auth-client";
|
|
3
|
+
const cachedToken = ref(null);
|
|
4
|
+
let cachedTokenVersion = null;
|
|
5
|
+
let pendingToken = null;
|
|
6
|
+
let pendingTokenVersion = null;
|
|
7
|
+
let initialTokenUsed = false;
|
|
8
|
+
const PENDING_AUTH_VERSION = "__pending__";
|
|
9
|
+
const useClientSession = () => authClient.useSession();
|
|
10
|
+
export function useAuth(initialToken) {
|
|
11
|
+
if (!initialTokenUsed && initialToken) {
|
|
12
|
+
cachedToken.value = initialToken;
|
|
13
|
+
cachedTokenVersion = PENDING_AUTH_VERSION;
|
|
14
|
+
initialTokenUsed = true;
|
|
15
|
+
}
|
|
16
|
+
const session = useClientSession();
|
|
17
|
+
const client = authClient;
|
|
18
|
+
const authVersion = computed(() => {
|
|
19
|
+
const data = session.value.data;
|
|
20
|
+
return data?.session?.id ?? data?.user?.id ?? null;
|
|
21
|
+
});
|
|
22
|
+
const currentAuthVersion = () => authVersion.value ?? (session.value.isPending ? PENDING_AUTH_VERSION : null);
|
|
23
|
+
const fetchAccessToken = async ({
|
|
24
|
+
forceRefreshToken = false
|
|
25
|
+
} = {}) => {
|
|
26
|
+
const version = currentAuthVersion();
|
|
27
|
+
if (version === null) {
|
|
28
|
+
cachedToken.value = null;
|
|
29
|
+
cachedTokenVersion = null;
|
|
30
|
+
pendingToken = null;
|
|
31
|
+
pendingTokenVersion = null;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (cachedToken.value && !forceRefreshToken && cachedTokenVersion === version) {
|
|
35
|
+
return cachedToken.value;
|
|
36
|
+
}
|
|
37
|
+
if (!forceRefreshToken && pendingToken && pendingTokenVersion === version) {
|
|
38
|
+
return pendingToken;
|
|
39
|
+
}
|
|
40
|
+
pendingTokenVersion = version;
|
|
41
|
+
pendingToken = client.convex.token({ fetchOptions: { throw: false } }).then(({ data }) => {
|
|
42
|
+
const token = data?.token || null;
|
|
43
|
+
cachedToken.value = token;
|
|
44
|
+
cachedTokenVersion = version;
|
|
45
|
+
return token;
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
cachedToken.value = null;
|
|
48
|
+
cachedTokenVersion = version;
|
|
49
|
+
return null;
|
|
50
|
+
}).finally(() => {
|
|
51
|
+
pendingToken = null;
|
|
52
|
+
pendingTokenVersion = null;
|
|
53
|
+
});
|
|
54
|
+
return pendingToken;
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
isLoading: computed(() => session.value.isPending && !cachedToken.value),
|
|
58
|
+
// Diverges from upstream's `Boolean(session?.session) || cachedToken !== null`:
|
|
59
|
+
// a settled signed-out session must read unauthenticated immediately (Vue has
|
|
60
|
+
// no re-render to run upstream's cache-clearing effect first), and Better Auth
|
|
61
|
+
// session data without a nested `session` still counts as signed in.
|
|
62
|
+
isAuthenticated: computed(
|
|
63
|
+
() => !!session.value.data || session.value.isPending && cachedToken.value !== null
|
|
64
|
+
),
|
|
65
|
+
fetchAccessToken,
|
|
66
|
+
// Vue-only service extensions (documented in PARITY.md).
|
|
67
|
+
client,
|
|
68
|
+
session,
|
|
69
|
+
user: computed(() => {
|
|
70
|
+
const data = session.value.data;
|
|
71
|
+
return data?.user ?? null;
|
|
72
|
+
}),
|
|
73
|
+
authVersion
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function __resetUseAuthForTests() {
|
|
77
|
+
cachedToken.value = null;
|
|
78
|
+
cachedTokenVersion = null;
|
|
79
|
+
pendingToken = null;
|
|
80
|
+
pendingTokenVersion = null;
|
|
81
|
+
initialTokenUsed = false;
|
|
82
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type ComputedRef, type PropType } from 'vue';
|
|
2
|
+
import { type ConvexAuthState, type IConvexVueClient } from '../../vue/auth/index.js';
|
|
3
|
+
export type UseAuth = () => {
|
|
4
|
+
isLoaded: ComputedRef<boolean>;
|
|
5
|
+
isSignedIn: ComputedRef<boolean | undefined>;
|
|
6
|
+
getToken: ComputedRef<(options: {
|
|
7
|
+
template?: 'convex';
|
|
8
|
+
skipCache?: boolean;
|
|
9
|
+
}) => Promise<string | null>>;
|
|
10
|
+
orgId: ComputedRef<string | undefined | null>;
|
|
11
|
+
orgRole: ComputedRef<string | undefined | null>;
|
|
12
|
+
sessionId: ComputedRef<string | undefined | null>;
|
|
13
|
+
sessionClaims: ComputedRef<Record<string, unknown> | undefined | null>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Options for {@link provideConvexAuthFromClerk} / `<ConvexProviderWithClerk>`.
|
|
17
|
+
*/
|
|
18
|
+
export interface ConvexProviderWithClerkOptions {
|
|
19
|
+
/** Convex client to authenticate. Defaults to the provided {@link useConvex} client. */
|
|
20
|
+
client?: IConvexVueClient;
|
|
21
|
+
/** Clerk's `useAuth` composable. Defaults to `useAuth` from `@clerk/vue`. */
|
|
22
|
+
useAuth?: UseAuth;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A composable which provides a {@link ConvexVueClient}
|
|
26
|
+
* authenticated with Clerk, exposing the reactive auth state to descendants
|
|
27
|
+
* via {@link useConvexAuth} — the Vue translation of wrapping the app in
|
|
28
|
+
* `convex/react-clerk`'s `<ConvexProviderWithClerk>`.
|
|
29
|
+
*
|
|
30
|
+
* Call it in a top-level component's `setup`. The app must be wrapped by a
|
|
31
|
+
* configured `clerkPlugin`, from `@clerk/vue`, `@clerk/nuxt` or
|
|
32
|
+
* another Vue-based Clerk client library and have the corresponding
|
|
33
|
+
* `useAuth` composable passed in (it defaults to `useAuth` from `@clerk/vue`).
|
|
34
|
+
*
|
|
35
|
+
* See [Convex Clerk](https://docs.convex.dev/auth/clerk) on how to set up
|
|
36
|
+
* Convex with Clerk.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```vue
|
|
40
|
+
* <script setup lang="ts">
|
|
41
|
+
* import { provideConvexAuthFromClerk } from 'nuxt-convex-module/clerk/client'
|
|
42
|
+
* provideConvexAuthFromClerk()
|
|
43
|
+
* </script>
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export declare function provideConvexAuthFromClerk(options?: ConvexProviderWithClerkOptions): ConvexAuthState;
|
|
49
|
+
/**
|
|
50
|
+
* A wrapper Vue component which provides a {@link ConvexVueClient}
|
|
51
|
+
* authenticated with Clerk — the component form of
|
|
52
|
+
* {@link provideConvexAuthFromClerk}, kept as a drop-in parity port of
|
|
53
|
+
* `convex/react-clerk`'s `<ConvexProviderWithClerk>`. Renders its default
|
|
54
|
+
* slot once Convex auth is wired.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
export declare const ConvexProviderWithClerk: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
59
|
+
client: {
|
|
60
|
+
type: PropType<IConvexVueClient>;
|
|
61
|
+
default: undefined;
|
|
62
|
+
};
|
|
63
|
+
useAuth: {
|
|
64
|
+
type: PropType<UseAuth>;
|
|
65
|
+
default: undefined;
|
|
66
|
+
};
|
|
67
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
}>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
70
|
+
client: {
|
|
71
|
+
type: PropType<IConvexVueClient>;
|
|
72
|
+
default: undefined;
|
|
73
|
+
};
|
|
74
|
+
useAuth: {
|
|
75
|
+
type: PropType<UseAuth>;
|
|
76
|
+
default: undefined;
|
|
77
|
+
};
|
|
78
|
+
}>> & Readonly<{}>, {
|
|
79
|
+
client: IConvexVueClient;
|
|
80
|
+
useAuth: UseAuth;
|
|
81
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useAuth as useClerkAuth } from "@clerk/vue";
|
|
2
|
+
import { defineComponent, toValue } from "vue";
|
|
3
|
+
import { provideConvexAuth } from "../../vue/auth/index.js";
|
|
4
|
+
import { useConvexOrThrow } from "../../vue/client.js";
|
|
5
|
+
export function provideConvexAuthFromClerk(options = {}) {
|
|
6
|
+
const client = options.client ?? useConvexOrThrow("provideConvexAuthFromClerk");
|
|
7
|
+
const useAuth = options.useAuth ?? useClerkAuth;
|
|
8
|
+
return provideConvexAuth({ client, useAuth: () => useAuthFromClerk(useAuth) });
|
|
9
|
+
}
|
|
10
|
+
export const ConvexProviderWithClerk = defineComponent({
|
|
11
|
+
name: "ConvexProviderWithClerk",
|
|
12
|
+
props: {
|
|
13
|
+
client: { type: Object, default: void 0 },
|
|
14
|
+
useAuth: { type: Function, default: void 0 }
|
|
15
|
+
},
|
|
16
|
+
setup(props, { slots }) {
|
|
17
|
+
provideConvexAuthFromClerk({ client: props.client, useAuth: props.useAuth });
|
|
18
|
+
return () => slots.default?.();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
function useAuthFromClerk(useAuth) {
|
|
22
|
+
const { isLoaded, isSignedIn, getToken, orgId, orgRole, sessionId, sessionClaims } = useAuth();
|
|
23
|
+
const fetchAccessToken = async ({ forceRefreshToken }) => {
|
|
24
|
+
try {
|
|
25
|
+
if (toValue(sessionClaims)?.aud === "convex") {
|
|
26
|
+
return await toValue(getToken)({
|
|
27
|
+
skipCache: forceRefreshToken
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
return await toValue(getToken)({
|
|
31
|
+
template: "convex",
|
|
32
|
+
skipCache: forceRefreshToken
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
isLoading: () => !toValue(isLoaded),
|
|
41
|
+
isAuthenticated: () => toValue(isSignedIn) ?? false,
|
|
42
|
+
fetchAccessToken,
|
|
43
|
+
// The `authVersion` key triggers setAuth() whenever these change — the Vue
|
|
44
|
+
// translation of upstream rebuilding `fetchAccessToken` with dependencies
|
|
45
|
+
// `[orgId, orgRole, sessionId]`. Anything else from the JWT Clerk wants to
|
|
46
|
+
// be reactive goes here too.
|
|
47
|
+
authVersion: () => `${toValue(orgId) ?? ""}:${toValue(orgRole) ?? ""}:${toValue(sessionId) ?? ""}`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConvexVueClient } from '../vue/client.js';
|
|
2
|
+
import type { ConvexDevtoolsBridgeHost } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Attach dev-only DevTools instrumentation to a {@link ConvexVueClient}.
|
|
5
|
+
*
|
|
6
|
+
* Observes the client from the outside — wrapping its subscription map,
|
|
7
|
+
* instance-patching `transition`/`close`, trapping `cachedSync` assignment for
|
|
8
|
+
* lazy connection-state wiring, and tee-ing the logger — and exposes plain-JSON
|
|
9
|
+
* snapshots plus change events for the DevTools iframe. Never touches the
|
|
10
|
+
* `sync` getter (which would open the WebSocket).
|
|
11
|
+
*/
|
|
12
|
+
export declare function createConvexDevtoolsBridge(client: ConvexVueClient): ConvexDevtoolsBridgeHost;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { convexToJson } from "convex/values";
|
|
2
|
+
const MAX_LOG_ENTRIES = 300;
|
|
3
|
+
function parseQueryToken(token) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = JSON.parse(token);
|
|
6
|
+
return {
|
|
7
|
+
udfPath: parsed.udfPath ?? token,
|
|
8
|
+
args: parsed.args ?? {},
|
|
9
|
+
paginated: parsed.type === "paginated"
|
|
10
|
+
};
|
|
11
|
+
} catch {
|
|
12
|
+
return { udfPath: token, args: void 0, paginated: false };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function toPlainJson(value) {
|
|
16
|
+
if (value === void 0) return void 0;
|
|
17
|
+
try {
|
|
18
|
+
return convexToJson(value);
|
|
19
|
+
} catch {
|
|
20
|
+
return String(value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function renderLogArg(arg) {
|
|
24
|
+
if (typeof arg === "string") return arg;
|
|
25
|
+
if (arg instanceof Error) return arg.stack ?? String(arg);
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(arg);
|
|
28
|
+
} catch {
|
|
29
|
+
return String(arg);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function serializeConnectionState(state) {
|
|
33
|
+
return {
|
|
34
|
+
isWebSocketConnected: state.isWebSocketConnected,
|
|
35
|
+
hasInflightRequests: state.hasInflightRequests,
|
|
36
|
+
hasEverConnected: state.hasEverConnected,
|
|
37
|
+
connectionCount: state.connectionCount,
|
|
38
|
+
connectionRetries: state.connectionRetries,
|
|
39
|
+
inflightMutations: state.inflightMutations,
|
|
40
|
+
inflightActions: state.inflightActions,
|
|
41
|
+
timeOfOldestInflightRequest: state.timeOfOldestInflightRequest?.getTime() ?? null
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function createConvexDevtoolsBridge(client) {
|
|
45
|
+
const internals = client;
|
|
46
|
+
const listeners = internals.listeners;
|
|
47
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
48
|
+
const emit = (event, payload) => {
|
|
49
|
+
for (const handler of handlers.get(event) ?? []) {
|
|
50
|
+
handler(payload);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
let closed = false;
|
|
54
|
+
const queryStats = /* @__PURE__ */ new Map();
|
|
55
|
+
const logs = [];
|
|
56
|
+
let auth = { available: false };
|
|
57
|
+
const computeQueries = () => {
|
|
58
|
+
const sync = internals.cachedSync;
|
|
59
|
+
return Array.from(listeners.entries(), ([token, callbacks]) => {
|
|
60
|
+
const { udfPath, args, paginated } = parseQueryToken(token);
|
|
61
|
+
const stats = queryStats.get(token);
|
|
62
|
+
const snapshot = {
|
|
63
|
+
token,
|
|
64
|
+
udfPath,
|
|
65
|
+
args,
|
|
66
|
+
paginated,
|
|
67
|
+
listenerCount: callbacks.size,
|
|
68
|
+
updates: stats?.updates ?? 0,
|
|
69
|
+
lastUpdatedAt: stats?.lastUpdatedAt ?? null
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
snapshot.result = toPlainJson(sync?.localQueryResultByToken?.(token));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
snapshot.errorMessage = error instanceof Error ? error.message : String(error);
|
|
75
|
+
}
|
|
76
|
+
const queryLogs = sync?.optimisticQueryResults?.queryLogs?.(token);
|
|
77
|
+
if (queryLogs?.length) snapshot.logs = queryLogs;
|
|
78
|
+
return snapshot;
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
let queriesEmitScheduled = false;
|
|
82
|
+
const scheduleQueriesEmit = () => {
|
|
83
|
+
if (queriesEmitScheduled || closed) return;
|
|
84
|
+
queriesEmitScheduled = true;
|
|
85
|
+
queueMicrotask(() => {
|
|
86
|
+
queriesEmitScheduled = false;
|
|
87
|
+
if (!closed) emit("queries", computeQueries());
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const originalSet = listeners.set.bind(listeners);
|
|
91
|
+
listeners.set = (key, value) => {
|
|
92
|
+
const result = originalSet(key, value);
|
|
93
|
+
scheduleQueriesEmit();
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
const originalDelete = listeners.delete.bind(listeners);
|
|
97
|
+
listeners.delete = (key) => {
|
|
98
|
+
const result = originalDelete(key);
|
|
99
|
+
queryStats.delete(key);
|
|
100
|
+
scheduleQueriesEmit();
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
const originalTransition = internals.transition.bind(client);
|
|
104
|
+
internals.transition = (updatedQueries) => {
|
|
105
|
+
originalTransition(updatedQueries);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
for (const token of updatedQueries) {
|
|
108
|
+
const stats = queryStats.get(token) ?? { updates: 0, lastUpdatedAt: now };
|
|
109
|
+
stats.updates += 1;
|
|
110
|
+
stats.lastUpdatedAt = now;
|
|
111
|
+
queryStats.set(token, stats);
|
|
112
|
+
}
|
|
113
|
+
scheduleQueriesEmit();
|
|
114
|
+
};
|
|
115
|
+
const computeConnection = () => {
|
|
116
|
+
if (closed) return { status: "closed" };
|
|
117
|
+
const sync = internals.cachedSync;
|
|
118
|
+
if (!sync) return { status: "idle" };
|
|
119
|
+
return { status: "active", state: serializeConnectionState(sync.connectionState()) };
|
|
120
|
+
};
|
|
121
|
+
let unsubscribeConnection;
|
|
122
|
+
const onSyncCreated = (sync) => {
|
|
123
|
+
if (unsubscribeConnection || closed) return;
|
|
124
|
+
unsubscribeConnection = sync.subscribeToConnectionState((state) => {
|
|
125
|
+
if (!closed) emit("connection", { status: "active", state: serializeConnectionState(state) });
|
|
126
|
+
});
|
|
127
|
+
emit("connection", computeConnection());
|
|
128
|
+
};
|
|
129
|
+
let cachedSyncValue = internals.cachedSync;
|
|
130
|
+
Object.defineProperty(client, "cachedSync", {
|
|
131
|
+
configurable: true,
|
|
132
|
+
get: () => cachedSyncValue,
|
|
133
|
+
set: (value) => {
|
|
134
|
+
cachedSyncValue = value;
|
|
135
|
+
if (value) onSyncCreated(value);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
if (cachedSyncValue) onSyncCreated(cachedSyncValue);
|
|
139
|
+
const originalClose = client.close.bind(client);
|
|
140
|
+
client.close = async () => {
|
|
141
|
+
closed = true;
|
|
142
|
+
unsubscribeConnection?.();
|
|
143
|
+
unsubscribeConnection = void 0;
|
|
144
|
+
emit("connection", { status: "closed" });
|
|
145
|
+
await originalClose();
|
|
146
|
+
};
|
|
147
|
+
const logger = client.logger;
|
|
148
|
+
const levels = [
|
|
149
|
+
["logVerbose", "verbose"],
|
|
150
|
+
["log", "log"],
|
|
151
|
+
["warn", "warn"],
|
|
152
|
+
["error", "error"]
|
|
153
|
+
];
|
|
154
|
+
for (const [method, level] of levels) {
|
|
155
|
+
const original = logger[method].bind(logger);
|
|
156
|
+
logger[method] = (...args) => {
|
|
157
|
+
original(...args);
|
|
158
|
+
if (closed) return;
|
|
159
|
+
const entry = { level, args: args.map(renderLogArg), timestamp: Date.now() };
|
|
160
|
+
logs.push(entry);
|
|
161
|
+
if (logs.length > MAX_LOG_ENTRIES) logs.splice(0, logs.length - MAX_LOG_ENTRIES);
|
|
162
|
+
emit("log", entry);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
version: 1,
|
|
167
|
+
getSnapshot() {
|
|
168
|
+
return {
|
|
169
|
+
connection: computeConnection(),
|
|
170
|
+
queries: computeQueries(),
|
|
171
|
+
auth,
|
|
172
|
+
logs: [...logs]
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
on(event, callback) {
|
|
176
|
+
let set = handlers.get(event);
|
|
177
|
+
if (!set) {
|
|
178
|
+
set = /* @__PURE__ */ new Set();
|
|
179
|
+
handlers.set(event, set);
|
|
180
|
+
}
|
|
181
|
+
set.add(callback);
|
|
182
|
+
return () => {
|
|
183
|
+
set.delete(callback);
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
setAuth(snapshot) {
|
|
187
|
+
auth = snapshot;
|
|
188
|
+
if (!closed) emit("auth", snapshot);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConvexDevtoolsBridge } from './types.js';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
__NUXT_CONVEX_DEVTOOLS__?: ConvexDevtoolsBridge;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#app";
|
|
2
|
+
import { inject, watchEffect } from "vue";
|
|
3
|
+
import { ConvexClientKey } from "../vue/client.js";
|
|
4
|
+
import { ConvexAuthStateKey } from "../vue/auth/index.js";
|
|
5
|
+
import { createConvexDevtoolsBridge } from "./bridge.js";
|
|
6
|
+
export default defineNuxtPlugin({
|
|
7
|
+
name: "nuxt-convex-module:devtools",
|
|
8
|
+
setup(nuxtApp) {
|
|
9
|
+
const attach = () => {
|
|
10
|
+
const client = nuxtApp.vueApp.runWithContext(() => inject(ConvexClientKey, void 0));
|
|
11
|
+
if (!client) return false;
|
|
12
|
+
const bridge = createConvexDevtoolsBridge(client);
|
|
13
|
+
const auth = nuxtApp.vueApp.runWithContext(() => inject(ConvexAuthStateKey, void 0));
|
|
14
|
+
if (auth) {
|
|
15
|
+
watchEffect(() => {
|
|
16
|
+
bridge.setAuth({
|
|
17
|
+
available: true,
|
|
18
|
+
isLoading: auth.isLoading.value,
|
|
19
|
+
isAuthenticated: auth.isAuthenticated.value,
|
|
20
|
+
isRefreshing: auth.isRefreshing.value
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
nuxtApp.provide("convexDevtools", bridge);
|
|
25
|
+
window.__NUXT_CONVEX_DEVTOOLS__ = bridge;
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
if (!attach()) {
|
|
29
|
+
nuxtApp.hook("app:mounted", () => {
|
|
30
|
+
attach();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Serialized `ConnectionState` (Date → epoch ms) plus the client lifecycle. */
|
|
2
|
+
export interface DevtoolsConnectionSnapshot {
|
|
3
|
+
/**
|
|
4
|
+
* `idle` — the underlying sync client (and its WebSocket) has not been
|
|
5
|
+
* created yet; `active` — it exists; `closed` — the client was closed.
|
|
6
|
+
*/
|
|
7
|
+
status: 'idle' | 'active' | 'closed';
|
|
8
|
+
state?: {
|
|
9
|
+
isWebSocketConnected: boolean;
|
|
10
|
+
hasInflightRequests: boolean;
|
|
11
|
+
hasEverConnected: boolean;
|
|
12
|
+
connectionCount: number;
|
|
13
|
+
connectionRetries: number;
|
|
14
|
+
inflightMutations: number;
|
|
15
|
+
inflightActions: number;
|
|
16
|
+
timeOfOldestInflightRequest: number | null;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** One active query subscription on the client. */
|
|
20
|
+
export interface DevtoolsQuerySnapshot {
|
|
21
|
+
/** The client's internal query token (stable identity for the panel). */
|
|
22
|
+
token: string;
|
|
23
|
+
udfPath: string;
|
|
24
|
+
/** Query args, JSON-serialized (`convexToJson` shape). */
|
|
25
|
+
args: unknown;
|
|
26
|
+
/** Whether this is a paginated-query page subscription. */
|
|
27
|
+
paginated: boolean;
|
|
28
|
+
/** Number of Vue-layer listeners sharing this subscription. */
|
|
29
|
+
listenerCount: number;
|
|
30
|
+
/** Server transitions observed for this token since the bridge attached. */
|
|
31
|
+
updates: number;
|
|
32
|
+
/** Epoch ms of the last observed transition, if any. */
|
|
33
|
+
lastUpdatedAt: number | null;
|
|
34
|
+
/** Current local result, JSON-serialized — absent while loading. */
|
|
35
|
+
result?: unknown;
|
|
36
|
+
/** Set instead of `result` when the query errored. */
|
|
37
|
+
errorMessage?: string;
|
|
38
|
+
/** Server-side log lines from this query's execution, if any. */
|
|
39
|
+
logs?: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface DevtoolsAuthSnapshot {
|
|
42
|
+
/** False when no app-level auth state is observable (e.g. Clerk/Auth0). */
|
|
43
|
+
available: boolean;
|
|
44
|
+
isLoading?: boolean;
|
|
45
|
+
isAuthenticated?: boolean;
|
|
46
|
+
isRefreshing?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface DevtoolsLogEntry {
|
|
49
|
+
level: 'verbose' | 'log' | 'warn' | 'error';
|
|
50
|
+
/** Log arguments, each rendered to a string. */
|
|
51
|
+
args: string[];
|
|
52
|
+
/** Epoch ms. */
|
|
53
|
+
timestamp: number;
|
|
54
|
+
}
|
|
55
|
+
export interface DevtoolsSnapshot {
|
|
56
|
+
connection: DevtoolsConnectionSnapshot;
|
|
57
|
+
queries: DevtoolsQuerySnapshot[];
|
|
58
|
+
auth: DevtoolsAuthSnapshot;
|
|
59
|
+
logs: DevtoolsLogEntry[];
|
|
60
|
+
}
|
|
61
|
+
export interface BridgeEvents {
|
|
62
|
+
connection: (connection: DevtoolsConnectionSnapshot) => void;
|
|
63
|
+
queries: (queries: DevtoolsQuerySnapshot[]) => void;
|
|
64
|
+
auth: (auth: DevtoolsAuthSnapshot) => void;
|
|
65
|
+
log: (entry: DevtoolsLogEntry) => void;
|
|
66
|
+
}
|
|
67
|
+
/** The surface the DevTools iframe consumes via `$convexDevtools`. */
|
|
68
|
+
export interface ConvexDevtoolsBridge {
|
|
69
|
+
version: 1;
|
|
70
|
+
getSnapshot(): DevtoolsSnapshot;
|
|
71
|
+
/** Subscribe to an event; returns an unsubscribe function. */
|
|
72
|
+
on<E extends keyof BridgeEvents>(event: E, callback: BridgeEvents[E]): () => void;
|
|
73
|
+
}
|
|
74
|
+
/** Host-side extras the plugin uses; not part of the iframe contract. */
|
|
75
|
+
export interface ConvexDevtoolsBridgeHost extends ConvexDevtoolsBridge {
|
|
76
|
+
/** Push the app-level auth state into the bridge (plugin-side). */
|
|
77
|
+
setAuth(auth: DevtoolsAuthSnapshot): void;
|
|
78
|
+
}
|
|
File without changes
|