use-convex 0.0.2 → 1.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/README.md +84 -27
- package/dist/module.d.mts +5 -3
- package/dist/module.mjs +35 -1
- package/dist/runtime/composables/useAuth.d.ts +5 -5
- package/dist/runtime/composables/useAuth.js +11 -24
- package/dist/runtime/composables/useAuthToken.d.ts +1 -1
- package/dist/runtime/composables/useAuthToken.js +2 -10
- package/dist/runtime/composables/useConvex.js +1 -1
- package/dist/runtime/composables/useConvexAction.js +6 -18
- package/dist/runtime/composables/useConvexAuth.js +4 -13
- package/dist/runtime/composables/useConvexFileUpload.d.ts +43 -0
- package/dist/runtime/composables/useConvexFileUpload.js +102 -0
- package/dist/runtime/composables/useConvexGate.js +3 -9
- package/dist/runtime/composables/useConvexMutation.js +7 -17
- package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +1 -1
- package/dist/runtime/composables/useConvexPaginatedQuery.js +7 -29
- package/dist/runtime/composables/useConvexQueries.d.ts +12 -7
- package/dist/runtime/composables/useConvexQueries.js +1 -3
- package/dist/runtime/composables/useConvexQuery.js +6 -12
- package/dist/runtime/composables/useConvexR2Upload.d.ts +40 -0
- package/dist/runtime/composables/useConvexR2Upload.js +86 -0
- package/dist/runtime/plugin.client.js +3 -7
- package/dist/runtime/plugin.devtools.client.d.ts +15 -2
- package/dist/runtime/plugin.devtools.client.js +18 -4
- package/dist/runtime/plugin.server.js +3 -7
- package/dist/runtime/server/api/convex/auth/session.js +7 -12
- package/dist/runtime/server/auth.js +1 -9
- package/dist/runtime/server/authCookies.js +8 -30
- package/dist/runtime/server/convexConfig.d.ts +12 -0
- package/dist/runtime/server/convexConfig.js +9 -0
- package/dist/runtime/server/fetch.d.ts +2 -4
- package/dist/runtime/server/fetch.js +3 -13
- package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -2
- package/dist/runtime/server/routes/__convex_devtools.get.js +184 -16
- package/dist/runtime/server/sameOrigin.js +1 -5
- package/dist/runtime/utils/authCookie.d.ts +0 -2
- package/dist/runtime/utils/authCookie.js +0 -1
- package/dist/runtime/utils/authState.js +1 -6
- package/dist/runtime/utils/authStorage.js +1 -1
- package/dist/runtime/utils/context.js +3 -2
- package/dist/runtime/utils/convexDashboard.d.ts +44 -0
- package/dist/runtime/utils/convexDashboard.js +77 -0
- package/dist/runtime/utils/errors.d.ts +14 -0
- package/dist/runtime/utils/errors.js +25 -0
- package/dist/runtime/utils/oauthRedirect.js +3 -2
- package/dist/runtime/utils/paginatedOptimistic.js +4 -22
- package/dist/runtime/utils/payloadCache.d.ts +6 -0
- package/dist/runtime/utils/payloadCache.js +7 -0
- package/dist/runtime/utils/pendingError.d.ts +9 -0
- package/dist/runtime/utils/pendingError.js +23 -0
- package/dist/types.d.mts +7 -3
- package/package.json +36 -20
|
@@ -1,35 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toValue } from "vue";
|
|
2
2
|
import { useConvexContext } from "../utils/context.js";
|
|
3
|
+
import { createPendingErrorState } from "../utils/pendingError.js";
|
|
3
4
|
export function useConvexMutation(mutation, options = {}) {
|
|
4
5
|
const ctx = useConvexContext();
|
|
5
|
-
const error =
|
|
6
|
-
const pendingCount = ref(0);
|
|
6
|
+
const { error, pending, withPending } = createPendingErrorState();
|
|
7
7
|
const mutate = async (args = {}) => {
|
|
8
8
|
if (import.meta.server || !ctx.client) {
|
|
9
|
-
throw new Error(
|
|
10
|
-
"[convex-nuxt] useConvexMutation can only run in the browser."
|
|
11
|
-
);
|
|
9
|
+
throw new Error("[use-convex] useConvexMutation can only run in the browser.");
|
|
12
10
|
}
|
|
13
|
-
|
|
14
|
-
error.value = null;
|
|
15
|
-
try {
|
|
11
|
+
return await withPending(async () => {
|
|
16
12
|
const resolved = toValue(args);
|
|
17
13
|
return await ctx.client.mutation(
|
|
18
14
|
mutation,
|
|
19
15
|
resolved,
|
|
20
16
|
options.optimisticUpdate ? { optimisticUpdate: options.optimisticUpdate } : void 0
|
|
21
17
|
);
|
|
22
|
-
}
|
|
23
|
-
const err = cause instanceof Error ? cause : new Error(String(cause));
|
|
24
|
-
error.value = err;
|
|
25
|
-
throw err;
|
|
26
|
-
} finally {
|
|
27
|
-
pendingCount.value--;
|
|
28
|
-
}
|
|
18
|
+
});
|
|
29
19
|
};
|
|
30
20
|
return {
|
|
31
21
|
mutate,
|
|
32
22
|
error,
|
|
33
|
-
pending
|
|
23
|
+
pending
|
|
34
24
|
};
|
|
35
25
|
}
|
|
@@ -6,7 +6,7 @@ import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
|
|
|
6
6
|
*/
|
|
7
7
|
export type PaginatedQueryReference = FunctionReference<'query', 'public', {
|
|
8
8
|
paginationOpts: PaginationOptions;
|
|
9
|
-
}, PaginationResult<
|
|
9
|
+
}, PaginationResult<unknown>>;
|
|
10
10
|
export type PaginatedQueryArgs<Query extends PaginatedQueryReference> = Omit<FunctionArgs<Query>, 'paginationOpts'>;
|
|
11
11
|
export type PaginatedQueryItem<Query extends PaginatedQueryReference> = FunctionReturnType<Query>['page'][number];
|
|
12
12
|
export type PaginationStatus = 'LoadingFirstPage' | 'CanLoadMore' | 'LoadingMore' | 'Exhausted';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
2
|
-
import { useAsyncData,
|
|
2
|
+
import { useAsyncData, useRuntimeConfig } from "nuxt/app";
|
|
3
3
|
import {
|
|
4
4
|
computed,
|
|
5
5
|
onScopeDispose,
|
|
@@ -10,24 +10,11 @@ import {
|
|
|
10
10
|
} from "vue";
|
|
11
11
|
import { resolveAuthGatedArgs } from "../utils/authGate.js";
|
|
12
12
|
import { useConvexContext } from "../utils/context.js";
|
|
13
|
+
import { readHydratedPayloadCache } from "../utils/payloadCache.js";
|
|
13
14
|
import { convexQueryKey } from "../utils/queryKey.js";
|
|
14
|
-
function mapLiveStatus(status) {
|
|
15
|
-
switch (status) {
|
|
16
|
-
case "LoadingFirstPage":
|
|
17
|
-
return "LoadingFirstPage";
|
|
18
|
-
case "LoadingMore":
|
|
19
|
-
return "LoadingMore";
|
|
20
|
-
case "Exhausted":
|
|
21
|
-
return "Exhausted";
|
|
22
|
-
case "CanLoadMore":
|
|
23
|
-
return "CanLoadMore";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
15
|
export async function useConvexPaginatedQuery(query, args = {}, options) {
|
|
27
16
|
if (options.initialNumItems <= 0) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
"[convex-nuxt] useConvexPaginatedQuery initialNumItems must be > 0"
|
|
30
|
-
);
|
|
17
|
+
throw new Error("[use-convex] useConvexPaginatedQuery initialNumItems must be > 0");
|
|
31
18
|
}
|
|
32
19
|
const runtimeConfig = useRuntimeConfig();
|
|
33
20
|
const defaultServer = runtimeConfig.public.convex?.server;
|
|
@@ -63,15 +50,10 @@ export async function useConvexPaginatedQuery(query, args = {}, options) {
|
|
|
63
50
|
}
|
|
64
51
|
const token = options.token ?? ctx.ssrToken.value;
|
|
65
52
|
if (requireAuth && import.meta.client && !token) {
|
|
66
|
-
|
|
67
|
-
const cached = nuxtApp.payload.data[toValue(key)];
|
|
68
|
-
return cached ?? null;
|
|
53
|
+
return readHydratedPayloadCache(key);
|
|
69
54
|
}
|
|
70
55
|
const http = ctx.createHttpClient({ token });
|
|
71
|
-
const result = await http.query(
|
|
72
|
-
query,
|
|
73
|
-
pageArgs
|
|
74
|
-
);
|
|
56
|
+
const result = await http.query(query, pageArgs);
|
|
75
57
|
return jsonToConvex(convexToJson(result));
|
|
76
58
|
},
|
|
77
59
|
{
|
|
@@ -104,7 +86,7 @@ export async function useConvexPaginatedQuery(query, args = {}, options) {
|
|
|
104
86
|
return "Exhausted";
|
|
105
87
|
}
|
|
106
88
|
if (liveReady.value && live.value) {
|
|
107
|
-
return
|
|
89
|
+
return live.value.status;
|
|
108
90
|
}
|
|
109
91
|
if (asyncData.pending.value || import.meta.client && !liveReady.value) {
|
|
110
92
|
if (asyncData.data.value) {
|
|
@@ -153,11 +135,7 @@ export async function useConvexPaginatedQuery(query, args = {}, options) {
|
|
|
153
135
|
);
|
|
154
136
|
};
|
|
155
137
|
watch(
|
|
156
|
-
() => [
|
|
157
|
-
toValue(args),
|
|
158
|
-
options.initialNumItems,
|
|
159
|
-
ctx.auth.isAuthenticated.value
|
|
160
|
-
],
|
|
138
|
+
() => [toValue(args), options.initialNumItems, ctx.auth.isAuthenticated.value],
|
|
161
139
|
subscribe,
|
|
162
140
|
{ immediate: true }
|
|
163
141
|
);
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import type { FunctionReference, FunctionReturnType } from 'convex/server';
|
|
2
|
-
import type { Value } from 'convex/values';
|
|
1
|
+
import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
|
|
3
2
|
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/** One entry in a {@link useConvexQueries} request map. */
|
|
4
|
+
export type ConvexQueryRequestEntry<Query extends FunctionReference<'query'> = FunctionReference<'query'>> = {
|
|
5
|
+
query: Query;
|
|
6
|
+
args: FunctionArgs<Query>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Request map for {@link useConvexQueries}.
|
|
10
|
+
* Values are `{ query, args }` or `'skip'`.
|
|
11
|
+
*/
|
|
12
|
+
export type ConvexQueriesRequest = Record<string, ConvexQueryRequestEntry | 'skip'>;
|
|
8
13
|
export type ConvexQueriesResult<Request extends ConvexQueriesRequest> = {
|
|
9
14
|
[K in keyof Request]: Request[K] extends {
|
|
10
15
|
query: infer Query;
|
|
11
|
-
args:
|
|
16
|
+
args: infer _Args;
|
|
12
17
|
} ? Query extends FunctionReference<'query'> ? FunctionReturnType<Query> | undefined | Error : undefined : undefined;
|
|
13
18
|
};
|
|
14
19
|
/**
|
|
@@ -15,9 +15,7 @@ export function useConvexQueries(queries) {
|
|
|
15
15
|
const ctx = useConvexContext();
|
|
16
16
|
const results = shallowRef({});
|
|
17
17
|
if (import.meta.server || !ctx.client) {
|
|
18
|
-
return computed(
|
|
19
|
-
() => results.value
|
|
20
|
-
);
|
|
18
|
+
return computed(() => results.value);
|
|
21
19
|
}
|
|
22
20
|
const client = ctx.client;
|
|
23
21
|
const unsubscribers = /* @__PURE__ */ new Map();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
2
|
-
import { useAsyncData,
|
|
2
|
+
import { useAsyncData, useRuntimeConfig } from "nuxt/app";
|
|
3
3
|
import {
|
|
4
4
|
computed,
|
|
5
5
|
onScopeDispose,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { resolveAuthGatedArgs } from "../utils/authGate.js";
|
|
11
11
|
import { useConvexContext } from "../utils/context.js";
|
|
12
12
|
import { resolveQueryOverlay } from "../utils/overlay.js";
|
|
13
|
+
import { readHydratedPayloadCache } from "../utils/payloadCache.js";
|
|
13
14
|
import { convexQueryKey } from "../utils/queryKey.js";
|
|
14
15
|
export async function useConvexQuery(query, args = {}, options = {}) {
|
|
15
16
|
const runtimeConfig = useRuntimeConfig();
|
|
@@ -23,9 +24,7 @@ export async function useConvexQuery(query, args = {}, options = {}) {
|
|
|
23
24
|
authenticated: requireAuth,
|
|
24
25
|
isAuthenticated: ctx.auth.isAuthenticated.value
|
|
25
26
|
});
|
|
26
|
-
const key = computed(
|
|
27
|
-
() => convexQueryKey(query, resolveRawArgs(), options.key)
|
|
28
|
-
);
|
|
27
|
+
const key = computed(() => convexQueryKey(query, resolveRawArgs(), options.key));
|
|
29
28
|
const asyncData = await useAsyncData(
|
|
30
29
|
key,
|
|
31
30
|
async () => {
|
|
@@ -35,15 +34,10 @@ export async function useConvexQuery(query, args = {}, options = {}) {
|
|
|
35
34
|
}
|
|
36
35
|
const token = options.token ?? ctx.ssrToken.value;
|
|
37
36
|
if (requireAuth && import.meta.client && !token) {
|
|
38
|
-
|
|
39
|
-
const cached = nuxtApp.payload.data[toValue(key)];
|
|
40
|
-
return cached ?? null;
|
|
37
|
+
return readHydratedPayloadCache(key);
|
|
41
38
|
}
|
|
42
39
|
const http = ctx.createHttpClient({ token });
|
|
43
|
-
const result = await http.query(
|
|
44
|
-
query,
|
|
45
|
-
argsValue ?? {}
|
|
46
|
-
);
|
|
40
|
+
const result = await http.query(query, argsValue ?? {});
|
|
47
41
|
return jsonToConvex(convexToJson(result));
|
|
48
42
|
},
|
|
49
43
|
{
|
|
@@ -87,7 +81,7 @@ export async function useConvexQuery(query, args = {}, options = {}) {
|
|
|
87
81
|
}
|
|
88
82
|
const client = ctx.client;
|
|
89
83
|
if (!client) {
|
|
90
|
-
throw new Error("[convex
|
|
84
|
+
throw new Error("[use-convex] ConvexClient is not available on the client.");
|
|
91
85
|
}
|
|
92
86
|
const liveReady = ref(false);
|
|
93
87
|
const liveData = ref();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FunctionReference, FunctionReturnType } from 'convex/server';
|
|
2
|
+
/**
|
|
3
|
+
* Subset of `@convex-dev/r2` `clientApi()` exports needed for client uploads.
|
|
4
|
+
* Pass `api.example` (or any module that re-exports these two mutations).
|
|
5
|
+
*/
|
|
6
|
+
export type ConvexR2UploadApi = {
|
|
7
|
+
generateUploadUrl: FunctionReference<'mutation'>;
|
|
8
|
+
syncMetadata: FunctionReference<'mutation'>;
|
|
9
|
+
};
|
|
10
|
+
export type ConvexR2UploadProgress = {
|
|
11
|
+
loaded: number;
|
|
12
|
+
total: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* PUT a file to an R2 signed URL (matches `@convex-dev/r2` client upload).
|
|
16
|
+
*/
|
|
17
|
+
export declare function putFileToR2UploadUrl(uploadUrl: string, file: File, onProgress?: (progress: ConvexR2UploadProgress) => void): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Vue counterpart of `@convex-dev/r2/react`'s `useUploadFile`.
|
|
20
|
+
*
|
|
21
|
+
* Expects the object returned by `r2.clientApi()` (at least
|
|
22
|
+
* `generateUploadUrl` + `syncMetadata`). Flow:
|
|
23
|
+
* 1. `generateUploadUrl()` → `{ url, key }`
|
|
24
|
+
* 2. PUT file bytes to the signed URL
|
|
25
|
+
* 3. `syncMetadata({ key })`
|
|
26
|
+
* 4. return `key`
|
|
27
|
+
*
|
|
28
|
+
* Does **not** depend on `@convex-dev/r2` at runtime — only on Convex
|
|
29
|
+
* function references your app exports. Prefer this over built-in
|
|
30
|
+
* `useConvexFileUpload` for large / resumable-oriented object storage.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useConvexR2Upload(api: ConvexR2UploadApi): {
|
|
33
|
+
upload: (file: File, options?: {
|
|
34
|
+
onProgress?: (progress: ConvexR2UploadProgress) => void;
|
|
35
|
+
}) => Promise<string>;
|
|
36
|
+
error: import("vue").Ref<Error | null, Error | null>;
|
|
37
|
+
pending: import("vue").ComputedRef<boolean>;
|
|
38
|
+
progress: import("vue").ComputedRef<number | null>;
|
|
39
|
+
};
|
|
40
|
+
export type { FunctionReturnType };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { useConvexContext } from "../utils/context.js";
|
|
3
|
+
function parseUploadUrlPayload(value) {
|
|
4
|
+
if (typeof value === "object" && value !== null && "url" in value && "key" in value && typeof value.url === "string" && typeof value.key === "string" && value.url.length > 0 && value.key.length > 0) {
|
|
5
|
+
return {
|
|
6
|
+
url: value.url,
|
|
7
|
+
key: value.key
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
throw new Error("[use-convex] generateUploadUrl must return `{ url, key }` strings.");
|
|
11
|
+
}
|
|
12
|
+
export function putFileToR2UploadUrl(uploadUrl, file, onProgress) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const xhr = new XMLHttpRequest();
|
|
15
|
+
xhr.open("PUT", uploadUrl);
|
|
16
|
+
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
17
|
+
xhr.upload.onprogress = (event) => {
|
|
18
|
+
if (!onProgress) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
onProgress({ loaded: event.loaded, total: event.total });
|
|
22
|
+
};
|
|
23
|
+
xhr.onload = () => {
|
|
24
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
25
|
+
resolve();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
reject(
|
|
29
|
+
new Error(`[use-convex] R2 upload failed (${xhr.status} ${xhr.statusText || "error"}).`)
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
xhr.onerror = () => {
|
|
33
|
+
reject(new Error("[use-convex] R2 upload network error."));
|
|
34
|
+
};
|
|
35
|
+
xhr.onabort = () => {
|
|
36
|
+
reject(new Error("[use-convex] R2 upload aborted."));
|
|
37
|
+
};
|
|
38
|
+
xhr.send(file);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function useConvexR2Upload(api) {
|
|
42
|
+
const ctx = useConvexContext();
|
|
43
|
+
const error = ref(null);
|
|
44
|
+
const pendingCount = ref(0);
|
|
45
|
+
const progress = ref(null);
|
|
46
|
+
const upload = async (file, options) => {
|
|
47
|
+
if (import.meta.server || !ctx.client) {
|
|
48
|
+
throw new Error("[use-convex] useConvexR2Upload can only run in the browser.");
|
|
49
|
+
}
|
|
50
|
+
if (!(file instanceof File)) {
|
|
51
|
+
throw new TypeError("[use-convex] useConvexR2Upload expects a File.");
|
|
52
|
+
}
|
|
53
|
+
pendingCount.value++;
|
|
54
|
+
error.value = null;
|
|
55
|
+
progress.value = null;
|
|
56
|
+
try {
|
|
57
|
+
const raw = await ctx.client.mutation(
|
|
58
|
+
api.generateUploadUrl,
|
|
59
|
+
{}
|
|
60
|
+
);
|
|
61
|
+
const { url, key } = parseUploadUrlPayload(raw);
|
|
62
|
+
await putFileToR2UploadUrl(url, file, (event) => {
|
|
63
|
+
if (event.total > 0) {
|
|
64
|
+
progress.value = event.loaded / event.total;
|
|
65
|
+
}
|
|
66
|
+
options?.onProgress?.(event);
|
|
67
|
+
});
|
|
68
|
+
progress.value = 1;
|
|
69
|
+
await ctx.client.mutation(api.syncMetadata, { key });
|
|
70
|
+
return key;
|
|
71
|
+
} catch (cause) {
|
|
72
|
+
const err = cause instanceof Error ? cause : new Error(String(cause));
|
|
73
|
+
error.value = err;
|
|
74
|
+
throw err;
|
|
75
|
+
} finally {
|
|
76
|
+
pendingCount.value--;
|
|
77
|
+
progress.value = null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
upload,
|
|
82
|
+
error,
|
|
83
|
+
pending: computed(() => pendingCount.value > 0),
|
|
84
|
+
progress: computed(() => progress.value)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -2,10 +2,8 @@ import { ConvexClient } from "convex/browser";
|
|
|
2
2
|
import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
|
|
3
3
|
import { useSsrTokenRef } from "./utils/authCookie.js";
|
|
4
4
|
import { createHttpClient } from "./utils/http.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
createAuthContext
|
|
8
|
-
} from "./utils/context.js";
|
|
5
|
+
import { convexNuxtKey, createAuthContext } from "./utils/context.js";
|
|
6
|
+
import { warnMissingConvexUrl } from "./utils/errors.js";
|
|
9
7
|
export default defineNuxtPlugin({
|
|
10
8
|
name: "convex-nuxt-client",
|
|
11
9
|
setup(nuxtApp) {
|
|
@@ -14,9 +12,7 @@ export default defineNuxtPlugin({
|
|
|
14
12
|
const url = convexConfig?.url;
|
|
15
13
|
if (!url) {
|
|
16
14
|
if (import.meta.dev) {
|
|
17
|
-
|
|
18
|
-
"[convex-nuxt] No Convex URL configured. Set convex.url or NUXT_PUBLIC_CONVEX_URL."
|
|
19
|
-
);
|
|
15
|
+
warnMissingConvexUrl("client");
|
|
20
16
|
}
|
|
21
17
|
return;
|
|
22
18
|
}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { useConvexConnectionState } from './composables/useConvexConnectionState.js';
|
|
2
|
+
export interface ConvexDevtoolsBridge {
|
|
3
|
+
connection: ReturnType<typeof useConvexConnectionState>;
|
|
4
|
+
/** Auth UI / Convex confirmation flags (dev inspection only). */
|
|
5
|
+
auth: {
|
|
6
|
+
configured: boolean;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
isAuthenticated: boolean;
|
|
9
|
+
isRefreshing: boolean;
|
|
10
|
+
isConvexAuthenticated: boolean | null;
|
|
11
|
+
hasSsrToken: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
1
14
|
/**
|
|
2
|
-
* Expose connection
|
|
3
|
-
* quick inspection alongside the DevTools iframe tab.
|
|
15
|
+
* Expose connection + auth snapshot on `window.__CONVEX_NUXT__` in development
|
|
16
|
+
* for quick inspection alongside the DevTools iframe tab.
|
|
4
17
|
*/
|
|
5
18
|
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
6
19
|
export default _default;
|
|
@@ -4,13 +4,27 @@ import { tryUseConvexContext } from "./utils/context.js";
|
|
|
4
4
|
export default defineNuxtPlugin({
|
|
5
5
|
name: "convex-nuxt-devtools",
|
|
6
6
|
setup() {
|
|
7
|
-
|
|
7
|
+
const ctx = tryUseConvexContext();
|
|
8
|
+
if (!import.meta.dev || !ctx) {
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
const connection = useConvexConnectionState();
|
|
11
|
-
if (typeof window
|
|
12
|
-
;
|
|
13
|
-
window.__CONVEX_NUXT__ = { connection };
|
|
12
|
+
if (typeof window === "undefined") {
|
|
13
|
+
return;
|
|
14
14
|
}
|
|
15
|
+
const bridge = {
|
|
16
|
+
connection,
|
|
17
|
+
get auth() {
|
|
18
|
+
return {
|
|
19
|
+
configured: ctx.auth.configured,
|
|
20
|
+
isLoading: ctx.auth.isLoading.value,
|
|
21
|
+
isAuthenticated: ctx.auth.isAuthenticated.value,
|
|
22
|
+
isRefreshing: ctx.auth.isRefreshing.value,
|
|
23
|
+
isConvexAuthenticated: ctx.auth.isConvexAuthenticated.value,
|
|
24
|
+
hasSsrToken: Boolean(ctx.ssrToken.value)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
window.__CONVEX_NUXT__ = bridge;
|
|
15
29
|
}
|
|
16
30
|
});
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
|
|
2
2
|
import { useSsrTokenRef } from "./utils/authCookie.js";
|
|
3
3
|
import { createHttpClient } from "./utils/http.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
createAuthContext
|
|
7
|
-
} from "./utils/context.js";
|
|
4
|
+
import { convexNuxtKey, createAuthContext } from "./utils/context.js";
|
|
5
|
+
import { warnMissingConvexUrl } from "./utils/errors.js";
|
|
8
6
|
export default defineNuxtPlugin({
|
|
9
7
|
name: "convex-nuxt-server",
|
|
10
8
|
setup(nuxtApp) {
|
|
@@ -13,9 +11,7 @@ export default defineNuxtPlugin({
|
|
|
13
11
|
const url = convexConfig?.url;
|
|
14
12
|
if (!url) {
|
|
15
13
|
if (import.meta.dev) {
|
|
16
|
-
|
|
17
|
-
"[convex-nuxt] No Convex URL configured. Set convex.url or NUXT_PUBLIC_CONVEX_URL."
|
|
18
|
-
);
|
|
14
|
+
warnMissingConvexUrl("server");
|
|
19
15
|
}
|
|
20
16
|
return;
|
|
21
17
|
}
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { ConvexHttpClient } from "convex/browser";
|
|
2
2
|
import { makeFunctionReference } from "convex/server";
|
|
3
|
-
import {
|
|
4
|
-
createError,
|
|
5
|
-
defineEventHandler,
|
|
6
|
-
readBody
|
|
7
|
-
} from "h3";
|
|
3
|
+
import { createError, defineEventHandler, readBody } from "h3";
|
|
8
4
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
9
|
-
import {
|
|
10
|
-
readHttpOnlyJwt,
|
|
11
|
-
readHttpOnlyRefresh,
|
|
12
|
-
setAuthCookies
|
|
13
|
-
} from "../../../authCookies.js";
|
|
5
|
+
import { readHttpOnlyJwt, readHttpOnlyRefresh, setAuthCookies } from "../../../authCookies.js";
|
|
14
6
|
import { isHttpOnlyAuth } from "../../../../utils/authStorage.js";
|
|
15
7
|
import { assertSameOrigin } from "../../../sameOrigin.js";
|
|
16
|
-
|
|
8
|
+
import { MISSING_URL_HINT } from "../../../../utils/errors.js";
|
|
9
|
+
const authSignIn = makeFunctionReference(
|
|
10
|
+
"auth:signIn"
|
|
11
|
+
);
|
|
17
12
|
function convexUrl(event) {
|
|
18
13
|
const config = useRuntimeConfig(event);
|
|
19
14
|
const url = config.public?.convex?.url ?? process.env.NUXT_PUBLIC_CONVEX_URL;
|
|
20
15
|
if (!url) {
|
|
21
16
|
throw createError({
|
|
22
17
|
statusCode: 500,
|
|
23
|
-
message:
|
|
18
|
+
message: MISSING_URL_HINT
|
|
24
19
|
});
|
|
25
20
|
}
|
|
26
21
|
return url;
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { createError, getCookie } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
2
|
import { resolveAuthCookieName } from "../utils/authStorage.js";
|
|
4
3
|
import { resolveFetchToken } from "../utils/fetchToken.js";
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
const config = useRuntimeConfig(event);
|
|
8
|
-
return config.public?.convex;
|
|
9
|
-
} catch {
|
|
10
|
-
return void 0;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
import { readConvexConfig } from "./convexConfig.js";
|
|
13
5
|
export function getConvexToken(event) {
|
|
14
6
|
const cookieName = resolveAuthCookieName(readConvexConfig(event)?.auth);
|
|
15
7
|
if (!cookieName) {
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deleteCookie,
|
|
3
|
-
getCookie,
|
|
4
|
-
getRequestHeader,
|
|
5
|
-
setCookie
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
1
|
+
import { deleteCookie, getCookie, getRequestHeader, setCookie } from "h3";
|
|
8
2
|
import {
|
|
9
3
|
AUTH_JWT_COOKIE_MAX_AGE,
|
|
10
4
|
isHttpOnlyAuth,
|
|
@@ -12,32 +6,18 @@ import {
|
|
|
12
6
|
resolveAuthPresentCookieName,
|
|
13
7
|
resolveAuthRefreshCookieName
|
|
14
8
|
} from "../utils/authStorage.js";
|
|
9
|
+
import { readConvexConfig } from "./convexConfig.js";
|
|
15
10
|
function readAuthConfig(event) {
|
|
16
|
-
|
|
17
|
-
const config = useRuntimeConfig(event);
|
|
18
|
-
return config.public?.convex?.auth;
|
|
19
|
-
} catch {
|
|
20
|
-
return void 0;
|
|
21
|
-
}
|
|
11
|
+
return readConvexConfig(event)?.auth;
|
|
22
12
|
}
|
|
23
13
|
function isLocalHost(event) {
|
|
24
14
|
const host = getRequestHeader(event, "host") ?? "";
|
|
25
15
|
return host.startsWith("localhost") || host.startsWith("127.0.0.1") || host.startsWith("[::1]");
|
|
26
16
|
}
|
|
27
|
-
function
|
|
28
|
-
const local = isLocalHost(event);
|
|
29
|
-
return {
|
|
30
|
-
httpOnly: true,
|
|
31
|
-
secure: !local,
|
|
32
|
-
sameSite: "lax",
|
|
33
|
-
path: "/",
|
|
34
|
-
maxAge: AUTH_JWT_COOKIE_MAX_AGE
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function readableCookieOptions(event) {
|
|
17
|
+
function cookieOptions(event, httpOnly) {
|
|
38
18
|
const local = isLocalHost(event);
|
|
39
19
|
return {
|
|
40
|
-
httpOnly
|
|
20
|
+
httpOnly,
|
|
41
21
|
secure: !local,
|
|
42
22
|
sameSite: "lax",
|
|
43
23
|
path: "/",
|
|
@@ -47,15 +27,13 @@ function readableCookieOptions(event) {
|
|
|
47
27
|
export function setAuthCookies(event, tokens) {
|
|
48
28
|
const auth = readAuthConfig(event);
|
|
49
29
|
if (!isHttpOnlyAuth(auth)) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
"[convex-nuxt] setAuthCookies requires convex.auth.httpOnly: true"
|
|
52
|
-
);
|
|
30
|
+
throw new Error("[use-convex] setAuthCookies requires convex.auth.httpOnly: true");
|
|
53
31
|
}
|
|
54
32
|
const jwtName = resolveAuthCookieName(auth);
|
|
55
33
|
const refreshName = resolveAuthRefreshCookieName(auth);
|
|
56
34
|
const presentName = resolveAuthPresentCookieName(auth);
|
|
57
|
-
const httpOpts =
|
|
58
|
-
const presentOpts =
|
|
35
|
+
const httpOpts = cookieOptions(event, true);
|
|
36
|
+
const presentOpts = cookieOptions(event, false);
|
|
59
37
|
if (!tokens.token) {
|
|
60
38
|
deleteCookie(event, jwtName, httpOpts);
|
|
61
39
|
deleteCookie(event, refreshName, httpOpts);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
export interface ConvexPublicConfig {
|
|
3
|
+
url?: string;
|
|
4
|
+
auth?: {
|
|
5
|
+
provider?: string;
|
|
6
|
+
cookie?: string;
|
|
7
|
+
httpOnly?: boolean;
|
|
8
|
+
presentCookie?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Read `runtimeConfig.public.convex`, or `undefined` outside a Nitro request. */
|
|
12
|
+
export declare function readConvexConfig(event?: H3Event): ConvexPublicConfig | undefined;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
-
import { resolveFetchToken } from '../utils/fetchToken.js';
|
|
4
|
-
export { resolveFetchToken };
|
|
5
3
|
export interface ConvexFetchOptions {
|
|
6
4
|
/**
|
|
7
5
|
* Deployment URL. Defaults to `NUXT_PUBLIC_CONVEX_URL` / runtimeConfig.
|
|
@@ -18,9 +16,9 @@ export interface ConvexFetchOptions {
|
|
|
18
16
|
event?: H3Event;
|
|
19
17
|
skipConvexDeploymentUrlCheck?: boolean;
|
|
20
18
|
}
|
|
21
|
-
/** @internal
|
|
19
|
+
/** @internal */
|
|
22
20
|
export declare function resolveUrl(options: ConvexFetchOptions): string;
|
|
23
|
-
/** @internal
|
|
21
|
+
/** @internal */
|
|
24
22
|
export declare function resolveToken(options: ConvexFetchOptions): string | undefined;
|
|
25
23
|
/**
|
|
26
24
|
* One-shot query via ConvexHttpClient. Use in Nitro routes / server middleware.
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { getCookie } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
2
|
import { createHttpClient } from "../utils/http.js";
|
|
4
3
|
import { resolveAuthCookieName } from "../utils/authStorage.js";
|
|
5
4
|
import { resolveFetchToken } from "../utils/fetchToken.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const config = useRuntimeConfig(event);
|
|
10
|
-
return config.public?.convex;
|
|
11
|
-
} catch {
|
|
12
|
-
return void 0;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
5
|
+
import { missingConvexUrlError } from "../utils/errors.js";
|
|
6
|
+
import { readConvexConfig } from "./convexConfig.js";
|
|
15
7
|
export function resolveUrl(options) {
|
|
16
8
|
if (options.url) {
|
|
17
9
|
return options.url;
|
|
@@ -24,9 +16,7 @@ export function resolveUrl(options) {
|
|
|
24
16
|
if (fromEnv) {
|
|
25
17
|
return fromEnv;
|
|
26
18
|
}
|
|
27
|
-
throw
|
|
28
|
-
"[convex-nuxt] No Convex URL. Pass { url } or set NUXT_PUBLIC_CONVEX_URL."
|
|
29
|
-
);
|
|
19
|
+
throw missingConvexUrlError("fetchQuery/Mutation/Action");
|
|
30
20
|
}
|
|
31
21
|
export function resolveToken(options) {
|
|
32
22
|
if (options.token !== void 0) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* DevTools iframe — embeds Convex dashboard (cloud) + public config tips.
|
|
3
|
+
* Live auth/connection: `window.__CONVEX_NUXT__` (devtools client plugin).
|
|
4
|
+
*
|
|
5
|
+
* Auto-login uses CONVEX_DEPLOY_KEY via postMessage to dashboard-embedded.
|
|
6
|
+
* Without a key, the embed shows Convex's credential form; Open dashboard
|
|
7
|
+
* still deep-links to dashboard.convex.dev (existing session).
|
|
4
8
|
*/
|
|
5
9
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, string>;
|
|
6
10
|
export default _default;
|