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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/chunks/index.mjs +64 -0
  4. package/dist/devtools-client/200.html +1 -0
  5. package/dist/devtools-client/404.html +1 -0
  6. package/dist/devtools-client/_nuxt/B29GGJ5n.js +1 -0
  7. package/dist/devtools-client/_nuxt/BSDEpQet.js +1 -0
  8. package/dist/devtools-client/_nuxt/BY23OQOo.js +1 -0
  9. package/dist/devtools-client/_nuxt/BZYRaL-G.js +1 -0
  10. package/dist/devtools-client/_nuxt/Byw6FxnC.js +1 -0
  11. package/dist/devtools-client/_nuxt/CJJ_hcZN.js +1 -0
  12. package/dist/devtools-client/_nuxt/CS6WMFOx.js +2 -0
  13. package/dist/devtools-client/_nuxt/CZbAGcNC.js +1 -0
  14. package/dist/devtools-client/_nuxt/Cr6_aklh.js +2 -0
  15. package/dist/devtools-client/_nuxt/DNfpyBVU.js +3 -0
  16. package/dist/devtools-client/_nuxt/DPWXUmcI.js +1 -0
  17. package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
  18. package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +1 -0
  19. package/dist/devtools-client/_nuxt/dy_xXcv9.js +1 -0
  20. package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +1 -0
  21. package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +1 -0
  22. package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +1 -0
  23. package/dist/devtools-client/_nuxt/ncgEdbEt.js +1 -0
  24. package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +1 -0
  25. package/dist/devtools-client/auth/index.html +1 -0
  26. package/dist/devtools-client/icon.svg +6 -0
  27. package/dist/devtools-client/index.html +1 -0
  28. package/dist/devtools-client/logs/index.html +1 -0
  29. package/dist/devtools-client/queries/index.html +1 -0
  30. package/dist/module.d.ts +189 -0
  31. package/dist/module.js +489 -0
  32. package/dist/module.json +13 -0
  33. package/dist/runtime/auth0/vue/index.d.ts +58 -0
  34. package/dist/runtime/auth0/vue/index.js +33 -0
  35. package/dist/runtime/better-auth/nuxt/middleware.d.ts +14 -0
  36. package/dist/runtime/better-auth/nuxt/middleware.js +39 -0
  37. package/dist/runtime/better-auth/nuxt/proxy.d.ts +6 -0
  38. package/dist/runtime/better-auth/nuxt/proxy.js +11 -0
  39. package/dist/runtime/better-auth/nuxt/server.d.ts +106 -0
  40. package/dist/runtime/better-auth/nuxt/server.js +123 -0
  41. package/dist/runtime/better-auth/vue/auth-boundary.d.ts +131 -0
  42. package/dist/runtime/better-auth/vue/auth-boundary.js +78 -0
  43. package/dist/runtime/better-auth/vue/client.d.ts +358 -0
  44. package/dist/runtime/better-auth/vue/client.js +5 -0
  45. package/dist/runtime/better-auth/vue/cross-domain.d.ts +39 -0
  46. package/dist/runtime/better-auth/vue/cross-domain.js +45 -0
  47. package/dist/runtime/better-auth/vue/hydration.d.ts +20 -0
  48. package/dist/runtime/better-auth/vue/hydration.js +52 -0
  49. package/dist/runtime/better-auth/vue/index.d.ts +12 -0
  50. package/dist/runtime/better-auth/vue/index.js +6 -0
  51. package/dist/runtime/better-auth/vue/plugin.client.d.ts +6 -0
  52. package/dist/runtime/better-auth/vue/plugin.client.js +32 -0
  53. package/dist/runtime/better-auth/vue/plugin.server.d.ts +24 -0
  54. package/dist/runtime/better-auth/vue/plugin.server.js +42 -0
  55. package/dist/runtime/better-auth/vue/use-auth.d.ts +92 -0
  56. package/dist/runtime/better-auth/vue/use-auth.js +82 -0
  57. package/dist/runtime/clerk/vue/index.d.ts +81 -0
  58. package/dist/runtime/clerk/vue/index.js +49 -0
  59. package/dist/runtime/devtools/bridge.d.ts +12 -0
  60. package/dist/runtime/devtools/bridge.js +191 -0
  61. package/dist/runtime/devtools/plugin.client.d.ts +8 -0
  62. package/dist/runtime/devtools/plugin.client.js +34 -0
  63. package/dist/runtime/devtools/types.d.ts +78 -0
  64. package/dist/runtime/devtools/types.js +0 -0
  65. package/dist/runtime/nuxt/composables/use-async-query.d.ts +144 -0
  66. package/dist/runtime/nuxt/composables/use-async-query.js +104 -0
  67. package/dist/runtime/nuxt/config.d.ts +7 -0
  68. package/dist/runtime/nuxt/config.js +12 -0
  69. package/dist/runtime/nuxt/index.d.ts +136 -0
  70. package/dist/runtime/nuxt/index.js +91 -0
  71. package/dist/runtime/polar/vue/components.d.ts +184 -0
  72. package/dist/runtime/polar/vue/components.js +115 -0
  73. package/dist/runtime/vue/auth/helpers.d.ts +67 -0
  74. package/dist/runtime/vue/auth/helpers.js +50 -0
  75. package/dist/runtime/vue/auth/index.d.ts +125 -0
  76. package/dist/runtime/vue/auth/index.js +97 -0
  77. package/dist/runtime/vue/client.d.ts +305 -0
  78. package/dist/runtime/vue/client.js +382 -0
  79. package/dist/runtime/vue/composables/use-action.d.ts +62 -0
  80. package/dist/runtime/vue/composables/use-action.js +13 -0
  81. package/dist/runtime/vue/composables/use-connection-state.d.ts +30 -0
  82. package/dist/runtime/vue/composables/use-connection-state.js +14 -0
  83. package/dist/runtime/vue/composables/use-mutation.d.ts +76 -0
  84. package/dist/runtime/vue/composables/use-mutation.js +34 -0
  85. package/dist/runtime/vue/composables/use-paginated-query.d.ts +132 -0
  86. package/dist/runtime/vue/composables/use-paginated-query.js +513 -0
  87. package/dist/runtime/vue/composables/use-queries.d.ts +62 -0
  88. package/dist/runtime/vue/composables/use-queries.js +37 -0
  89. package/dist/runtime/vue/composables/use-query.d.ts +87 -0
  90. package/dist/runtime/vue/composables/use-query.js +53 -0
  91. package/dist/runtime/vue/composables/use-storage-url.d.ts +46 -0
  92. package/dist/runtime/vue/composables/use-storage-url.js +9 -0
  93. package/dist/runtime/vue/composables/use-subscription.d.ts +15 -0
  94. package/dist/runtime/vue/composables/use-subscription.js +33 -0
  95. package/dist/runtime/vue/composables/use-upload-queue.d.ts +117 -0
  96. package/dist/runtime/vue/composables/use-upload-queue.js +115 -0
  97. package/dist/runtime/vue/composables/use-upload.d.ts +134 -0
  98. package/dist/runtime/vue/composables/use-upload.js +114 -0
  99. package/dist/runtime/vue/hydration.d.ts +66 -0
  100. package/dist/runtime/vue/hydration.js +46 -0
  101. package/dist/runtime/vue/index.d.ts +94 -0
  102. package/dist/runtime/vue/index.js +42 -0
  103. package/dist/runtime/vue/plugin.d.ts +18 -0
  104. package/dist/runtime/vue/plugin.js +16 -0
  105. package/dist/runtime/vue/provide.d.ts +39 -0
  106. package/dist/runtime/vue/provide.js +15 -0
  107. package/dist/runtime/vue/queries-observer.d.ts +41 -0
  108. package/dist/runtime/vue/queries-observer.js +126 -0
  109. package/dist/types.d.ts +3 -0
  110. 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