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,32 +1,200 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { buildDevtoolsDashboardPayload } from "../../utils/convexDashboard.js";
|
|
3
4
|
export default defineEventHandler((event) => {
|
|
4
5
|
const config = useRuntimeConfig(event);
|
|
5
6
|
const convex = config.public?.convex ?? {};
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const devtools = config.convexDevtools;
|
|
8
|
+
const url = typeof convex.url === "string" && convex.url.trim() || typeof devtools?.url === "string" && devtools.url.trim() || process.env.NUXT_PUBLIC_CONVEX_URL?.trim() || process.env.CONVEX_URL?.trim() || null;
|
|
9
|
+
const hasUrl = Boolean(url);
|
|
10
|
+
const summary = {
|
|
11
|
+
url,
|
|
12
|
+
server: convex.server ?? true,
|
|
13
|
+
auth: convex.auth ? {
|
|
14
|
+
provider: convex.auth.provider ?? null,
|
|
15
|
+
cookie: convex.auth.cookie ?? null,
|
|
16
|
+
httpOnly: convex.auth.httpOnly ?? false,
|
|
17
|
+
presentCookie: convex.auth.presentCookie ?? null
|
|
18
|
+
} : null
|
|
19
|
+
};
|
|
20
|
+
const tips = [
|
|
21
|
+
hasUrl ? "Deployment URL is set." : "No URL \u2014 set convex.url or NUXT_PUBLIC_CONVEX_URL.",
|
|
22
|
+
"Set CONVEX_DEPLOY_KEY for dashboard auto-login in this tab (dev only).",
|
|
23
|
+
"In the app console: window.__CONVEX_NUXT__.connection (WebSocket).",
|
|
24
|
+
"In the app console: window.__CONVEX_NUXT__.auth (gate flags).",
|
|
25
|
+
"Gate private live queries with { authenticated: true }.",
|
|
26
|
+
"SSR off globally: convex.server: false \u2014 or per call { server: false }."
|
|
27
|
+
];
|
|
28
|
+
const dashboard = buildDevtoolsDashboardPayload({
|
|
29
|
+
url,
|
|
30
|
+
convexDeployment: devtools?.deployment || process.env.CONVEX_DEPLOYMENT,
|
|
31
|
+
deployKey: devtools?.deployKey || process.env.CONVEX_DEPLOY_KEY
|
|
32
|
+
});
|
|
33
|
+
const configJson = JSON.stringify({ config: summary, tips }).replace(/</g, "\\u003c");
|
|
34
|
+
const dashboardJson = JSON.stringify({
|
|
35
|
+
deploymentUrl: dashboard.deploymentUrl,
|
|
36
|
+
deploymentName: dashboard.deploymentName,
|
|
37
|
+
adminKey: dashboard.adminKey,
|
|
38
|
+
embed: dashboard.embed,
|
|
39
|
+
embedSrc: dashboard.embedSrc,
|
|
40
|
+
embedOrigin: dashboard.embedOrigin
|
|
41
|
+
}).replace(/</g, "\\u003c");
|
|
42
|
+
const openHref = dashboard.openDashboardUrl ? escapeHtml(dashboard.openDashboardUrl) : null;
|
|
43
|
+
const urlLabel = dashboard.deploymentUrl ? escapeHtml(dashboard.deploymentUrl) : "No deployment URL";
|
|
44
|
+
const statusLabel = !dashboard.embed ? "Hosted embed needs a *.convex.cloud URL" : dashboard.adminKey ? "Auto-login via CONVEX_DEPLOY_KEY" : "Paste credentials in the embed, or Open dashboard";
|
|
45
|
+
const embedBlock = dashboard.embed ? `<div id="embed-wrap" class="embed${dashboard.adminKey ? " pending" : ""}">
|
|
46
|
+
<iframe
|
|
47
|
+
id="convex-dashboard"
|
|
48
|
+
title="Convex dashboard"
|
|
49
|
+
src="${escapeHtml(dashboard.embedSrc)}"
|
|
50
|
+
allow="clipboard-write"
|
|
51
|
+
></iframe>
|
|
52
|
+
</div>` : `<div class="notice">
|
|
53
|
+
<p>The hosted Convex dashboard embed only works with Convex Cloud URLs (<code>*.convex.cloud</code>).</p>
|
|
54
|
+
<p>Local / self-hosted deployments: use the CLI dashboard or Open dashboard when a name is known.</p>
|
|
55
|
+
</div>`;
|
|
16
56
|
return `<!doctype html>
|
|
17
57
|
<html lang="en">
|
|
18
58
|
<head>
|
|
19
59
|
<meta charset="utf-8" />
|
|
20
|
-
<title>
|
|
60
|
+
<title>use-convex \xB7 DevTools</title>
|
|
21
61
|
<style>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
62
|
+
* { box-sizing: border-box; }
|
|
63
|
+
html, body {
|
|
64
|
+
height: 100%;
|
|
65
|
+
margin: 0;
|
|
66
|
+
}
|
|
67
|
+
body {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
71
|
+
color: #e5e7eb;
|
|
72
|
+
background: #0b1220;
|
|
73
|
+
}
|
|
74
|
+
header {
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
padding: 0.65rem 0.85rem;
|
|
77
|
+
border-bottom: 1px solid #1f2937;
|
|
78
|
+
background: #0f172a;
|
|
79
|
+
}
|
|
80
|
+
.row {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
align-items: baseline;
|
|
84
|
+
gap: 0.5rem 0.85rem;
|
|
85
|
+
}
|
|
86
|
+
h1 {
|
|
87
|
+
font-size: 0.9rem;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
margin: 0;
|
|
90
|
+
color: #93c5fd;
|
|
91
|
+
}
|
|
92
|
+
.url {
|
|
93
|
+
font-size: 0.7rem;
|
|
94
|
+
color: #9ca3af;
|
|
95
|
+
word-break: break-all;
|
|
96
|
+
}
|
|
97
|
+
.status {
|
|
98
|
+
font-size: 0.7rem;
|
|
99
|
+
color: #a5b4fc;
|
|
100
|
+
}
|
|
101
|
+
a.open {
|
|
102
|
+
font-size: 0.75rem;
|
|
103
|
+
color: #67e8f9;
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
}
|
|
106
|
+
a.open:hover { text-decoration: underline; }
|
|
107
|
+
details {
|
|
108
|
+
margin-top: 0.45rem;
|
|
109
|
+
}
|
|
110
|
+
summary {
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
font-size: 0.7rem;
|
|
113
|
+
color: #9ca3af;
|
|
114
|
+
user-select: none;
|
|
115
|
+
}
|
|
116
|
+
pre {
|
|
117
|
+
margin: 0.4rem 0 0;
|
|
118
|
+
white-space: pre-wrap;
|
|
119
|
+
word-break: break-word;
|
|
120
|
+
font-size: 0.72rem;
|
|
121
|
+
line-height: 1.4;
|
|
122
|
+
max-height: 12rem;
|
|
123
|
+
overflow: auto;
|
|
124
|
+
}
|
|
125
|
+
.embed {
|
|
126
|
+
flex: 1 1 auto;
|
|
127
|
+
min-height: 0;
|
|
128
|
+
position: relative;
|
|
129
|
+
}
|
|
130
|
+
.embed.pending iframe { visibility: hidden; }
|
|
131
|
+
iframe {
|
|
132
|
+
display: block;
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 100%;
|
|
135
|
+
border: 0;
|
|
136
|
+
background: #111827;
|
|
137
|
+
}
|
|
138
|
+
.notice {
|
|
139
|
+
flex: 1 1 auto;
|
|
140
|
+
padding: 1rem 0.85rem;
|
|
141
|
+
font-size: 0.8rem;
|
|
142
|
+
color: #9ca3af;
|
|
143
|
+
line-height: 1.5;
|
|
144
|
+
}
|
|
145
|
+
.notice code { color: #93c5fd; }
|
|
25
146
|
</style>
|
|
26
147
|
</head>
|
|
27
148
|
<body>
|
|
28
|
-
<
|
|
29
|
-
|
|
149
|
+
<header>
|
|
150
|
+
<div class="row">
|
|
151
|
+
<h1>use-convex</h1>
|
|
152
|
+
<span class="url">${urlLabel}</span>
|
|
153
|
+
${openHref ? `<a class="open" href="${openHref}" target="_blank" rel="noopener noreferrer">Open dashboard \u2197</a>` : ""}
|
|
154
|
+
</div>
|
|
155
|
+
<p class="status">${escapeHtml(statusLabel)}</p>
|
|
156
|
+
<details>
|
|
157
|
+
<summary>Module config & tips</summary>
|
|
158
|
+
<pre id="config-pre"></pre>
|
|
159
|
+
</details>
|
|
160
|
+
</header>
|
|
161
|
+
${embedBlock}
|
|
162
|
+
<script>
|
|
163
|
+
(function () {
|
|
164
|
+
var config = ${configJson};
|
|
165
|
+
var dash = ${dashboardJson};
|
|
166
|
+
var pre = document.getElementById('config-pre');
|
|
167
|
+
if (pre) pre.textContent = JSON.stringify(config, null, 2);
|
|
168
|
+
|
|
169
|
+
if (!dash.embed || !dash.adminKey) return;
|
|
170
|
+
|
|
171
|
+
var iframe = document.getElementById('convex-dashboard');
|
|
172
|
+
var wrap = document.getElementById('embed-wrap');
|
|
173
|
+
if (!iframe || !iframe.contentWindow) return;
|
|
174
|
+
|
|
175
|
+
function reveal() {
|
|
176
|
+
if (wrap) wrap.classList.remove('pending');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
window.addEventListener('message', function (event) {
|
|
180
|
+
if (event.origin !== dash.embedOrigin) return;
|
|
181
|
+
if (!event.data || event.data.type !== 'dashboard-credentials-request') return;
|
|
182
|
+
iframe.contentWindow.postMessage(
|
|
183
|
+
{
|
|
184
|
+
type: 'dashboard-credentials',
|
|
185
|
+
adminKey: dash.adminKey,
|
|
186
|
+
deploymentUrl: dash.deploymentUrl,
|
|
187
|
+
deploymentName: dash.deploymentName,
|
|
188
|
+
},
|
|
189
|
+
dash.embedOrigin,
|
|
190
|
+
);
|
|
191
|
+
reveal();
|
|
192
|
+
});
|
|
193
|
+
})();
|
|
194
|
+
<\/script>
|
|
30
195
|
</body>
|
|
31
196
|
</html>`;
|
|
32
197
|
});
|
|
198
|
+
function escapeHtml(value) {
|
|
199
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
200
|
+
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createError,
|
|
3
|
-
getRequestHeader,
|
|
4
|
-
getRequestProtocol
|
|
5
|
-
} from "h3";
|
|
1
|
+
import { createError, getRequestHeader, getRequestProtocol } from "h3";
|
|
6
2
|
export function assertSameOrigin(event) {
|
|
7
3
|
const secFetchSite = getRequestHeader(event, "sec-fetch-site");
|
|
8
4
|
if (secFetchSite === "same-origin" || secFetchSite === "none") {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { type ComputedRef } from 'vue';
|
|
2
|
-
import { AUTH_JWT_COOKIE_MAX_AGE } from './authStorage.js';
|
|
3
|
-
export { AUTH_JWT_COOKIE_MAX_AGE };
|
|
4
2
|
/**
|
|
5
3
|
* Shared JWT cookie for SSR snapshots. Same name + options as persistTokens
|
|
6
4
|
* so plugin reads and sign-in/out writes share one Nuxt cookie ref.
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
export function resolveConvexAuthState(flags) {
|
|
2
|
-
const {
|
|
3
|
-
authProviderLoading,
|
|
4
|
-
authProviderAuthenticated,
|
|
5
|
-
isConvexAuthenticated,
|
|
6
|
-
isRefreshing
|
|
7
|
-
} = flags;
|
|
2
|
+
const { authProviderLoading, authProviderAuthenticated, isConvexAuthenticated, isRefreshing } = flags;
|
|
8
3
|
if (authProviderLoading) {
|
|
9
4
|
return {
|
|
10
5
|
isLoading: true,
|
|
@@ -7,7 +7,7 @@ export const DEFAULT_AUTH_PRESENT_COOKIE = "convex_auth_present";
|
|
|
7
7
|
export const HTTPONLY_JWT_COOKIE = "__convexAuthJWT";
|
|
8
8
|
export const HTTPONLY_REFRESH_COOKIE = "__convexAuthRefreshToken";
|
|
9
9
|
export function storageNamespace(url) {
|
|
10
|
-
return url.replace(/[^a-
|
|
10
|
+
return url.replace(/[^a-z0-9]/gi, "");
|
|
11
11
|
}
|
|
12
12
|
export function storageKey(base, url) {
|
|
13
13
|
return `${base}_${storageNamespace(url)}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inject, ref } from "vue";
|
|
2
2
|
import { useNuxtApp } from "nuxt/app";
|
|
3
|
+
import { missingConvexUrlError } from "./errors.js";
|
|
3
4
|
export const convexNuxtKey = Symbol("convex-nuxt");
|
|
4
5
|
export function createAuthContext() {
|
|
5
6
|
return {
|
|
@@ -33,8 +34,8 @@ export function tryUseConvexContext() {
|
|
|
33
34
|
export function useConvexContext() {
|
|
34
35
|
const ctx = tryUseConvexContext();
|
|
35
36
|
if (!ctx) {
|
|
36
|
-
throw
|
|
37
|
-
"
|
|
37
|
+
throw missingConvexUrlError(
|
|
38
|
+
"Convex plugin did not start (the module no-ops when the URL is empty)"
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
return ctx;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for linking / embedding the Convex dashboard from Nuxt DevTools.
|
|
3
|
+
* Pure — no Nitro / Nuxt imports so unit tests stay lightweight.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DASHBOARD_EMBED_ORIGIN = "https://dashboard-embedded.convex.dev";
|
|
6
|
+
export declare const DASHBOARD_EMBED_DATA_URL = "https://dashboard-embedded.convex.dev/data";
|
|
7
|
+
export declare const DASHBOARD_HOST = "https://dashboard.convex.dev";
|
|
8
|
+
/**
|
|
9
|
+
* Strip CLI prefixes like `dev:happy-animal-123` → `happy-animal-123`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function deploymentNameFromEnv(value: string | undefined | null): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Derive deployment name from a cloud URL host: `happy-animal-123.convex.cloud`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deploymentNameFromUrl(url: string | undefined | null): string | null;
|
|
16
|
+
export declare function resolveDeploymentName(options: {
|
|
17
|
+
url?: string | null;
|
|
18
|
+
convexDeployment?: string | null;
|
|
19
|
+
}): string | null;
|
|
20
|
+
/** True when the URL looks like Convex Cloud (embeddable hosted dashboard). */
|
|
21
|
+
export declare function isConvexCloudUrl(url: string | undefined | null): boolean;
|
|
22
|
+
/** True for a loopback Convex backend (`npx convex dev --local`). */
|
|
23
|
+
export declare function isLocalConvexUrl(url: string | undefined | null): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* When CONVEX_DEPLOYMENT is a cloud `dev:`/`prod:` name but the app URL is
|
|
26
|
+
* still loopback (CLI: "Can't safely modify .env.local"), return the cloud URL.
|
|
27
|
+
*/
|
|
28
|
+
export declare function suggestedCloudUrlFromMismatch(url: string | undefined | null, convexDeployment: string | undefined | null): string | null;
|
|
29
|
+
export declare function dashboardDeepLink(deploymentName: string | null): string | null;
|
|
30
|
+
export interface DevtoolsDashboardPayload {
|
|
31
|
+
deploymentUrl: string | null;
|
|
32
|
+
deploymentName: string | null;
|
|
33
|
+
/** Present only when CONVEX_DEPLOY_KEY is set (dev server only). */
|
|
34
|
+
adminKey: string | null;
|
|
35
|
+
embed: boolean;
|
|
36
|
+
openDashboardUrl: string | null;
|
|
37
|
+
embedSrc: string;
|
|
38
|
+
embedOrigin: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function buildDevtoolsDashboardPayload(options: {
|
|
41
|
+
url?: string | null;
|
|
42
|
+
convexDeployment?: string | null;
|
|
43
|
+
deployKey?: string | null;
|
|
44
|
+
}): DevtoolsDashboardPayload;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const DASHBOARD_EMBED_ORIGIN = "https://dashboard-embedded.convex.dev";
|
|
2
|
+
export const DASHBOARD_EMBED_DATA_URL = `${DASHBOARD_EMBED_ORIGIN}/data`;
|
|
3
|
+
export const DASHBOARD_HOST = "https://dashboard.convex.dev";
|
|
4
|
+
export function deploymentNameFromEnv(value) {
|
|
5
|
+
if (!value) return null;
|
|
6
|
+
const trimmed = value.split("#")[0]?.trim() ?? "";
|
|
7
|
+
if (!trimmed) return null;
|
|
8
|
+
const colon = trimmed.indexOf(":");
|
|
9
|
+
if (colon >= 0 && colon < trimmed.length - 1) {
|
|
10
|
+
return trimmed.slice(colon + 1);
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
export function deploymentNameFromUrl(url) {
|
|
15
|
+
if (!url) return null;
|
|
16
|
+
try {
|
|
17
|
+
const { hostname } = new URL(url);
|
|
18
|
+
if (hostname.endsWith(".convex.cloud")) {
|
|
19
|
+
const name = hostname.slice(0, -".convex.cloud".length);
|
|
20
|
+
return name || null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resolveDeploymentName(options) {
|
|
28
|
+
return deploymentNameFromUrl(options.url) ?? deploymentNameFromEnv(options.convexDeployment) ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function isConvexCloudUrl(url) {
|
|
31
|
+
if (!url) return false;
|
|
32
|
+
try {
|
|
33
|
+
const { hostname } = new URL(url);
|
|
34
|
+
return hostname.endsWith(".convex.cloud");
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function isLocalConvexUrl(url) {
|
|
40
|
+
if (!url) return false;
|
|
41
|
+
try {
|
|
42
|
+
const { hostname } = new URL(url);
|
|
43
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "[::1]";
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function suggestedCloudUrlFromMismatch(url, convexDeployment) {
|
|
49
|
+
if (!isLocalConvexUrl(url)) return null;
|
|
50
|
+
const trimmed = convexDeployment?.split("#")[0]?.trim() ?? "";
|
|
51
|
+
if (!/^(dev|prod):/.test(trimmed)) return null;
|
|
52
|
+
const name = deploymentNameFromEnv(trimmed);
|
|
53
|
+
if (!name) return null;
|
|
54
|
+
return `https://${name}.convex.cloud`;
|
|
55
|
+
}
|
|
56
|
+
export function dashboardDeepLink(deploymentName) {
|
|
57
|
+
if (!deploymentName) return null;
|
|
58
|
+
return `${DASHBOARD_HOST}/d/${encodeURIComponent(deploymentName)}`;
|
|
59
|
+
}
|
|
60
|
+
export function buildDevtoolsDashboardPayload(options) {
|
|
61
|
+
const deploymentUrl = options.url?.trim() || null;
|
|
62
|
+
const deploymentName = resolveDeploymentName({
|
|
63
|
+
url: deploymentUrl,
|
|
64
|
+
convexDeployment: options.convexDeployment
|
|
65
|
+
});
|
|
66
|
+
const adminKey = options.deployKey?.trim() || null;
|
|
67
|
+
const embed = isConvexCloudUrl(deploymentUrl);
|
|
68
|
+
return {
|
|
69
|
+
deploymentUrl,
|
|
70
|
+
deploymentName,
|
|
71
|
+
adminKey: embed && adminKey ? adminKey : null,
|
|
72
|
+
embed,
|
|
73
|
+
openDashboardUrl: dashboardDeepLink(deploymentName),
|
|
74
|
+
embedSrc: DASHBOARD_EMBED_DATA_URL,
|
|
75
|
+
embedOrigin: DASHBOARD_EMBED_ORIGIN
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consistent, actionable errors for consumers of `use-convex`.
|
|
3
|
+
*
|
|
4
|
+
* Branding uses `[use-convex]` (package name). Older `[use-convex]` strings
|
|
5
|
+
* are migrated here so setup failures point to the same fix.
|
|
6
|
+
*/
|
|
7
|
+
export declare const USE_CONVEX_PREFIX = "[use-convex]";
|
|
8
|
+
/** Missing deployment URL — the most common first-run failure. */
|
|
9
|
+
export declare const MISSING_URL_HINT = "Set convex.url in nuxt.config or NUXT_PUBLIC_CONVEX_URL (runtimeConfig.public.convex.url).";
|
|
10
|
+
export declare function useConvexError(message: string): Error;
|
|
11
|
+
export declare function missingConvexUrlError(context?: string): Error;
|
|
12
|
+
export declare function warnMissingConvexUrl(scope: 'client' | 'server'): void;
|
|
13
|
+
export declare function unreachableConvexUrlError(convexUrl: string): Error;
|
|
14
|
+
export declare function warnStaleLocalConvexUrl(localUrl: string, cloudUrl: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const USE_CONVEX_PREFIX = "[use-convex]";
|
|
2
|
+
export const MISSING_URL_HINT = "Set convex.url in nuxt.config or NUXT_PUBLIC_CONVEX_URL (runtimeConfig.public.convex.url).";
|
|
3
|
+
export function useConvexError(message) {
|
|
4
|
+
const text = message.startsWith(USE_CONVEX_PREFIX) ? message : `${USE_CONVEX_PREFIX} ${message}`;
|
|
5
|
+
return new Error(text);
|
|
6
|
+
}
|
|
7
|
+
export function missingConvexUrlError(context) {
|
|
8
|
+
const where = context ? `${context}: ` : "";
|
|
9
|
+
return useConvexError(`${where}No Convex URL. ${MISSING_URL_HINT}`);
|
|
10
|
+
}
|
|
11
|
+
export function warnMissingConvexUrl(scope) {
|
|
12
|
+
console.warn(
|
|
13
|
+
`${USE_CONVEX_PREFIX} No Convex URL on ${scope}. ${MISSING_URL_HINT} The module no-ops until a URL is set.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export function unreachableConvexUrlError(convexUrl) {
|
|
17
|
+
return useConvexError(
|
|
18
|
+
`Could not reach Convex at ${convexUrl}. If you switched from a local to a cloud deployment, set CONVEX_URL and NUXT_PUBLIC_CONVEX_URL in .env.local to the URL printed by \`npx convex dev\` and restart Nuxt. If you use a local backend, keep \`npx convex dev\` running.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
export function warnStaleLocalConvexUrl(localUrl, cloudUrl) {
|
|
22
|
+
console.warn(
|
|
23
|
+
`${USE_CONVEX_PREFIX} Convex URL is ${localUrl} but CONVEX_DEPLOYMENT is a cloud deployment. The Convex CLI could not update .env.local \u2014 set CONVEX_URL and NUXT_PUBLIC_CONVEX_URL to ${cloudUrl} and restart Nuxt.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { useConvexError } from "./errors.js";
|
|
1
2
|
export function parseOAuthRedirect(redirect) {
|
|
2
3
|
let url;
|
|
3
4
|
try {
|
|
4
5
|
url = new URL(redirect);
|
|
5
6
|
} catch {
|
|
6
|
-
throw
|
|
7
|
+
throw useConvexError("Invalid OAuth redirect URL");
|
|
7
8
|
}
|
|
8
9
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
9
|
-
throw
|
|
10
|
+
throw useConvexError("OAuth redirect must be http(s)");
|
|
10
11
|
}
|
|
11
12
|
return url;
|
|
12
13
|
}
|
|
@@ -13,9 +13,7 @@ export function optimisticallyUpdateValueInPaginatedQuery(localStore, query, arg
|
|
|
13
13
|
if (typeof value === "object" && value !== null && Array.isArray(value.page)) {
|
|
14
14
|
localStore.setQuery(query, queryResult.args, {
|
|
15
15
|
...value,
|
|
16
|
-
page: value.page.map(
|
|
17
|
-
updateValue
|
|
18
|
-
)
|
|
16
|
+
page: value.page.map(updateValue)
|
|
19
17
|
});
|
|
20
18
|
}
|
|
21
19
|
}
|
|
@@ -55,9 +53,7 @@ export function insertAtBottomIfLoaded(options) {
|
|
|
55
53
|
(key) => compareValues(argsToMatch[key], q.args[key]) === 0
|
|
56
54
|
);
|
|
57
55
|
});
|
|
58
|
-
const lastPage = matching.find(
|
|
59
|
-
(q) => q.value !== void 0 && q.value.isDone
|
|
60
|
-
);
|
|
56
|
+
const lastPage = matching.find((q) => q.value !== void 0 && q.value.isDone);
|
|
61
57
|
if (lastPage === void 0 || lastPage.value === void 0) {
|
|
62
58
|
return;
|
|
63
59
|
}
|
|
@@ -67,14 +63,7 @@ export function insertAtBottomIfLoaded(options) {
|
|
|
67
63
|
});
|
|
68
64
|
}
|
|
69
65
|
export function insertAtPosition(options) {
|
|
70
|
-
const {
|
|
71
|
-
paginatedQuery,
|
|
72
|
-
sortOrder,
|
|
73
|
-
sortKeyFromItem,
|
|
74
|
-
localQueryStore,
|
|
75
|
-
item,
|
|
76
|
-
argsToMatch
|
|
77
|
-
} = options;
|
|
66
|
+
const { paginatedQuery, sortOrder, sortKeyFromItem, localQueryStore, item, argsToMatch } = options;
|
|
78
67
|
const queries = localQueryStore.getAllQueries(paginatedQuery);
|
|
79
68
|
const queryGroups = {};
|
|
80
69
|
for (const query of queries) {
|
|
@@ -107,14 +96,7 @@ export function insertAtPosition(options) {
|
|
|
107
96
|
}
|
|
108
97
|
}
|
|
109
98
|
function insertAtPositionInPages(options) {
|
|
110
|
-
const {
|
|
111
|
-
pageQueries,
|
|
112
|
-
sortOrder,
|
|
113
|
-
sortKeyFromItem,
|
|
114
|
-
localQueryStore,
|
|
115
|
-
item,
|
|
116
|
-
paginatedQuery
|
|
117
|
-
} = options;
|
|
99
|
+
const { pageQueries, sortOrder, sortKeyFromItem, localQueryStore, item, paginatedQuery } = options;
|
|
118
100
|
const insertedKey = sortKeyFromItem(item);
|
|
119
101
|
const loadedPages = pageQueries.filter(
|
|
120
102
|
(q) => q.value !== void 0 && q.value.page.length > 0
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* When HttpOnly auth leaves the client without a JWT for HttpClient, keep the
|
|
4
|
+
* hydrated Nuxt payload and let the live subscription own updates.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readHydratedPayloadCache<T>(key: MaybeRefOrGetter<string>): T | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pending/error state for browser-only Convex client operations
|
|
3
|
+
* (`useConvexMutation`, `useConvexAction`).
|
|
4
|
+
*/
|
|
5
|
+
export declare function createPendingErrorState(): {
|
|
6
|
+
error: import("vue").Ref<Error | null, Error | null>;
|
|
7
|
+
pending: import("vue").ComputedRef<boolean>;
|
|
8
|
+
withPending: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
export function createPendingErrorState() {
|
|
3
|
+
const error = ref(null);
|
|
4
|
+
const pendingCount = ref(0);
|
|
5
|
+
async function withPending(fn) {
|
|
6
|
+
pendingCount.value++;
|
|
7
|
+
error.value = null;
|
|
8
|
+
try {
|
|
9
|
+
return await fn();
|
|
10
|
+
} catch (cause) {
|
|
11
|
+
const err = cause instanceof Error ? cause : new Error(String(cause));
|
|
12
|
+
error.value = err;
|
|
13
|
+
throw err;
|
|
14
|
+
} finally {
|
|
15
|
+
pendingCount.value--;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
error,
|
|
20
|
+
pending: computed(() => pendingCount.value > 0),
|
|
21
|
+
withPending
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/types.d.mts
CHANGED
|
@@ -10,11 +10,15 @@ export { type ConvexQueryArgs, type UseConvexQueryOptions, type UseConvexQueryRe
|
|
|
10
10
|
|
|
11
11
|
export { type AuthTokenFetcher, type UseConvexAuthReturn, type UseConvexAuthSetupOptions } from '../dist/runtime/composables/useConvexAuth.js'
|
|
12
12
|
|
|
13
|
-
export { type UseConvexMutationOptions } from '../dist/runtime/composables/useConvexMutation.js'
|
|
13
|
+
export { type OptimisticUpdate, type UseConvexMutationOptions } from '../dist/runtime/composables/useConvexMutation.js'
|
|
14
14
|
|
|
15
|
-
export { type
|
|
15
|
+
export { type ConvexFileUploadExtraArgs, type ConvexFileUploadMeta, type UseConvexFileUploadOptions } from '../dist/runtime/composables/useConvexFileUpload.js'
|
|
16
16
|
|
|
17
|
-
export { type
|
|
17
|
+
export { type ConvexR2UploadApi, type ConvexR2UploadProgress } from '../dist/runtime/composables/useConvexR2Upload.js'
|
|
18
|
+
|
|
19
|
+
export { type PaginatedQueryArgs, type PaginatedQueryItem, type PaginatedQueryReference, type PaginationStatus, type UseConvexPaginatedQueryOptions, type UseConvexPaginatedQueryReturn } from '../dist/runtime/composables/useConvexPaginatedQuery.js'
|
|
20
|
+
|
|
21
|
+
export { type ConvexQueriesRequest, type ConvexQueriesResult, type ConvexQueryRequestEntry } from '../dist/runtime/composables/useConvexQueries.js'
|
|
18
22
|
|
|
19
23
|
export { type ConvexFetchOptions } from '../dist/runtime/server/fetch.js'
|
|
20
24
|
|