better-convex 0.0.5 → 0.2.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/dist/auth-client/index.d.ts +3 -0
- package/dist/auth-client/index.js +141 -92
- package/dist/auth-nextjs/index.d.ts +1 -0
- package/dist/{auth-store-DANFmEdk.js → auth-store-BMSJFMtR.js} +28 -69
- package/dist/cli.cjs +1 -1
- package/dist/{codegen-DN-vbQih.cjs → codegen-Cca0gj6d.cjs} +53 -6
- package/dist/crpc/index.d.ts +41 -2
- package/dist/crpc/index.js +121 -1
- package/dist/http-types-BwEQgYV1.d.ts +369 -0
- package/dist/react/index.d.ts +255 -39
- package/dist/react/index.js +325 -56
- package/dist/rsc/index.d.ts +96 -8
- package/dist/rsc/index.js +98 -7
- package/dist/server/index.d.ts +114 -86
- package/dist/server/index.js +695 -128
- package/dist/{types-CotWvon8.d.ts → types-DLVNrXKq.d.ts} +9 -17
- package/dist/watcher.cjs +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,9 @@ type ConvexAuthProviderProps = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Unified auth provider for Convex + Better Auth.
|
|
27
27
|
* Handles token sync, HMR persistence, and auth callbacks.
|
|
28
|
+
*
|
|
29
|
+
* Structure: AuthProvider wraps ConvexAuthProviderInner so that
|
|
30
|
+
* useAuthStore() is available when creating fetchAccessToken.
|
|
28
31
|
*/
|
|
29
32
|
declare function ConvexAuthProvider({
|
|
30
33
|
children,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { _ as defaultIsUnauthorized,
|
|
2
|
+
import { _ as defaultIsUnauthorized, d as useAuthStore, f as useAuthValue, g as CRPCClientError, r as FetchAccessTokenContext, s as decodeJwtExp, t as AuthProvider } from "../auth-store-BMSJFMtR.js";
|
|
3
3
|
import { c } from "react/compiler-runtime";
|
|
4
|
-
import { ConvexProviderWithAuth } from "convex/react";
|
|
4
|
+
import { ConvexProviderWithAuth, useConvexAuth } from "convex/react";
|
|
5
5
|
import { useCallback, useEffect, useMemo } from "react";
|
|
6
|
-
import { jsx
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
|
|
8
8
|
//#region src/auth-client/convex-auth-provider.tsx
|
|
9
9
|
/**
|
|
@@ -18,122 +18,171 @@ const defaultMutationHandler = () => {
|
|
|
18
18
|
/**
|
|
19
19
|
* Unified auth provider for Convex + Better Auth.
|
|
20
20
|
* Handles token sync, HMR persistence, and auth callbacks.
|
|
21
|
+
*
|
|
22
|
+
* Structure: AuthProvider wraps ConvexAuthProviderInner so that
|
|
23
|
+
* useAuthStore() is available when creating fetchAccessToken.
|
|
21
24
|
*/
|
|
22
25
|
function ConvexAuthProvider(t0) {
|
|
23
|
-
const $ = c(
|
|
26
|
+
const $ = c(15);
|
|
24
27
|
const { children, client, authClient, initialToken, onMutationUnauthorized, onQueryUnauthorized, isUnauthorized } = t0;
|
|
28
|
+
useOTTHandler(authClient);
|
|
25
29
|
let t1;
|
|
26
30
|
if ($[0] !== initialToken) {
|
|
27
|
-
t1 = initialToken
|
|
31
|
+
t1 = initialToken ? decodeJwtExp(initialToken) : null;
|
|
28
32
|
$[0] = initialToken;
|
|
29
33
|
$[1] = t1;
|
|
30
34
|
} else t1 = $[1];
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let t4;
|
|
38
|
-
if ($[2] !== t3) {
|
|
39
|
-
t4 = {
|
|
40
|
-
isLoading: true,
|
|
41
|
-
token: t3
|
|
35
|
+
const t2 = initialToken ?? null;
|
|
36
|
+
let t3;
|
|
37
|
+
if ($[2] !== t1 || $[3] !== t2) {
|
|
38
|
+
t3 = {
|
|
39
|
+
expiresAt: t1,
|
|
40
|
+
token: t2
|
|
42
41
|
};
|
|
43
|
-
$[2] =
|
|
44
|
-
$[3] =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
t9 = /* @__PURE__ */ jsxs(AuthProvider, {
|
|
58
|
-
initialValues: t4,
|
|
59
|
-
isUnauthorized: t5,
|
|
60
|
-
onMutationUnauthorized: t6,
|
|
61
|
-
onQueryUnauthorized: t7,
|
|
62
|
-
children: [t8, children]
|
|
42
|
+
$[2] = t1;
|
|
43
|
+
$[3] = t2;
|
|
44
|
+
$[4] = t3;
|
|
45
|
+
} else t3 = $[4];
|
|
46
|
+
const tokenValues = t3;
|
|
47
|
+
const t4 = isUnauthorized ?? defaultIsUnauthorized;
|
|
48
|
+
const t5 = onMutationUnauthorized ?? defaultMutationHandler;
|
|
49
|
+
const t6 = onQueryUnauthorized ?? _temp;
|
|
50
|
+
let t7;
|
|
51
|
+
if ($[5] !== authClient || $[6] !== children || $[7] !== client) {
|
|
52
|
+
t7 = /* @__PURE__ */ jsx(ConvexAuthProviderInner, {
|
|
53
|
+
authClient,
|
|
54
|
+
client,
|
|
55
|
+
children
|
|
63
56
|
});
|
|
57
|
+
$[5] = authClient;
|
|
64
58
|
$[6] = children;
|
|
65
|
-
$[7] =
|
|
66
|
-
$[8] =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
useAuth,
|
|
77
|
-
children: t9
|
|
59
|
+
$[7] = client;
|
|
60
|
+
$[8] = t7;
|
|
61
|
+
} else t7 = $[8];
|
|
62
|
+
let t8;
|
|
63
|
+
if ($[9] !== t4 || $[10] !== t5 || $[11] !== t6 || $[12] !== t7 || $[13] !== tokenValues) {
|
|
64
|
+
t8 = /* @__PURE__ */ jsx(AuthProvider, {
|
|
65
|
+
initialValues: tokenValues,
|
|
66
|
+
isUnauthorized: t4,
|
|
67
|
+
onMutationUnauthorized: t5,
|
|
68
|
+
onQueryUnauthorized: t6,
|
|
69
|
+
children: t7
|
|
78
70
|
});
|
|
79
|
-
$[
|
|
80
|
-
$[
|
|
81
|
-
$[
|
|
82
|
-
$[
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
$[9] = t4;
|
|
72
|
+
$[10] = t5;
|
|
73
|
+
$[11] = t6;
|
|
74
|
+
$[12] = t7;
|
|
75
|
+
$[13] = tokenValues;
|
|
76
|
+
$[14] = t8;
|
|
77
|
+
} else t8 = $[14];
|
|
78
|
+
return t8;
|
|
85
79
|
}
|
|
86
80
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
81
|
+
* Inner provider that has access to AuthStore via useAuthStore().
|
|
82
|
+
* Creates fetchAccessToken and passes it through context (no race condition).
|
|
89
83
|
*/
|
|
90
84
|
function _temp() {}
|
|
91
|
-
function
|
|
92
|
-
const session = authClient.useSession();
|
|
85
|
+
function ConvexAuthProviderInner({ children, client, authClient }) {
|
|
93
86
|
const authStore = useAuthStore();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
authStore.set("token",
|
|
99
|
-
|
|
87
|
+
const { data: session, isPending } = authClient.useSession();
|
|
88
|
+
const fetchAccessToken = useCallback(async ({ forceRefreshToken = false } = {}) => {
|
|
89
|
+
if (!session) {
|
|
90
|
+
if (!isPending) {
|
|
91
|
+
authStore.set("token", null);
|
|
92
|
+
authStore.set("expiresAt", null);
|
|
100
93
|
}
|
|
94
|
+
return authStore.get("token");
|
|
95
|
+
}
|
|
96
|
+
const cachedToken = authStore.get("token");
|
|
97
|
+
const expiresAt = authStore.get("expiresAt");
|
|
98
|
+
const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
|
|
99
|
+
if (!forceRefreshToken && cachedToken && expiresAt && timeRemaining >= 6e4) return cachedToken;
|
|
100
|
+
try {
|
|
101
|
+
const { data } = await authClient.convex.token();
|
|
102
|
+
const jwt = data?.token || null;
|
|
103
|
+
if (jwt) {
|
|
104
|
+
const exp = decodeJwtExp(jwt);
|
|
105
|
+
authStore.set("token", jwt);
|
|
106
|
+
authStore.set("expiresAt", exp);
|
|
107
|
+
}
|
|
108
|
+
return jwt;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
101
111
|
}
|
|
102
112
|
}, [
|
|
103
|
-
session
|
|
104
|
-
|
|
105
|
-
authStore
|
|
113
|
+
session,
|
|
114
|
+
isPending,
|
|
115
|
+
authStore,
|
|
116
|
+
authClient
|
|
106
117
|
]);
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Creates useAuth hook for ConvexProviderWithAuth.
|
|
111
|
-
* Uses auth-store token as single source of truth.
|
|
112
|
-
*/
|
|
113
|
-
function useCreateConvexAuth(authClient) {
|
|
114
|
-
return useMemo(() => function useConvexAuth$1() {
|
|
115
|
-
const { data: session, isPending: isSessionPending } = authClient.useSession();
|
|
116
|
-
const token = useAuthValue("token");
|
|
117
|
-
const sessionId = session?.session?.id;
|
|
118
|
-
const fetchAccessToken = useCallback(async ({ forceRefreshToken = false } = {}) => {
|
|
119
|
-
if (token && !forceRefreshToken) return token;
|
|
120
|
-
try {
|
|
121
|
-
const { data } = await authClient.convex.token();
|
|
122
|
-
return data?.token || null;
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}, [sessionId, token]);
|
|
118
|
+
const useAuth = useMemo(() => function useConvexAuthHook() {
|
|
127
119
|
return useMemo(() => ({
|
|
128
|
-
isLoading:
|
|
120
|
+
isLoading: isPending,
|
|
129
121
|
isAuthenticated: session !== null,
|
|
130
122
|
fetchAccessToken
|
|
131
123
|
}), [
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
isPending,
|
|
125
|
+
session,
|
|
134
126
|
fetchAccessToken
|
|
135
127
|
]);
|
|
136
|
-
}, [
|
|
128
|
+
}, [
|
|
129
|
+
isPending,
|
|
130
|
+
session,
|
|
131
|
+
fetchAccessToken
|
|
132
|
+
]);
|
|
133
|
+
return /* @__PURE__ */ jsx(FetchAccessTokenContext.Provider, {
|
|
134
|
+
value: fetchAccessToken,
|
|
135
|
+
children: /* @__PURE__ */ jsx(ConvexProviderWithAuth, {
|
|
136
|
+
client,
|
|
137
|
+
useAuth,
|
|
138
|
+
children: /* @__PURE__ */ jsx(AuthStateSync, { children })
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Syncs auth state from useConvexAuth() to the auth store.
|
|
144
|
+
* MUST be inside ConvexProviderWithAuth to access useConvexAuth().
|
|
145
|
+
*
|
|
146
|
+
* Defensive isLoading computation handles SSR hydration race:
|
|
147
|
+
* 1. SSR sets token from cookie
|
|
148
|
+
* 2. Client hydrates
|
|
149
|
+
* 3. Better Auth's useSession() briefly returns null before loading cookie
|
|
150
|
+
* 4. Convex sets isConvexAuthenticated = false (no auth to wait for)
|
|
151
|
+
* 5. Without defensive check, we'd sync { isLoading: false, isAuthenticated: false }
|
|
152
|
+
* 6. Queries would throw UNAUTHORIZED before token is validated
|
|
153
|
+
*/
|
|
154
|
+
function AuthStateSync(t0) {
|
|
155
|
+
const $ = c(6);
|
|
156
|
+
const { children } = t0;
|
|
157
|
+
const { isLoading: convexIsLoading, isAuthenticated } = useConvexAuth();
|
|
158
|
+
const authStore = useAuthStore();
|
|
159
|
+
const token = useAuthValue("token");
|
|
160
|
+
let t1;
|
|
161
|
+
let t2;
|
|
162
|
+
if ($[0] !== authStore || $[1] !== convexIsLoading || $[2] !== isAuthenticated || $[3] !== token) {
|
|
163
|
+
t1 = () => {
|
|
164
|
+
const isLoading = convexIsLoading || !!token && !isAuthenticated;
|
|
165
|
+
authStore.set("isLoading", isLoading);
|
|
166
|
+
authStore.set("isAuthenticated", isAuthenticated);
|
|
167
|
+
};
|
|
168
|
+
t2 = [
|
|
169
|
+
convexIsLoading,
|
|
170
|
+
isAuthenticated,
|
|
171
|
+
token,
|
|
172
|
+
authStore
|
|
173
|
+
];
|
|
174
|
+
$[0] = authStore;
|
|
175
|
+
$[1] = convexIsLoading;
|
|
176
|
+
$[2] = isAuthenticated;
|
|
177
|
+
$[3] = token;
|
|
178
|
+
$[4] = t1;
|
|
179
|
+
$[5] = t2;
|
|
180
|
+
} else {
|
|
181
|
+
t1 = $[4];
|
|
182
|
+
t2 = $[5];
|
|
183
|
+
}
|
|
184
|
+
useEffect(t1, t2);
|
|
185
|
+
return children;
|
|
137
186
|
}
|
|
138
187
|
/**
|
|
139
188
|
* Handles cross-domain one-time token (OTT) verification.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { c } from "react/compiler-runtime";
|
|
3
3
|
import { useConvexAuth } from "convex/react";
|
|
4
|
-
import
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
5
|
import { createAtomStore } from "jotai-x";
|
|
6
6
|
|
|
7
7
|
//#region src/crpc/error.ts
|
|
@@ -40,59 +40,21 @@ const defaultIsUnauthorized = (error) => {
|
|
|
40
40
|
* Provides token storage and auth callback configuration.
|
|
41
41
|
* App configures handlers, lib hooks consume state.
|
|
42
42
|
*/
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (typeof window === "undefined") return null;
|
|
48
|
-
return globalStore[AUTH_TOKEN_KEY] ?? null;
|
|
43
|
+
const FetchAccessTokenContext = createContext(null);
|
|
44
|
+
/** Get fetchAccessToken from context (available immediately, no race condition) */
|
|
45
|
+
const useFetchAccessToken = () => {
|
|
46
|
+
return useContext(FetchAccessTokenContext);
|
|
49
47
|
};
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const { isAuthenticated, isLoading } = useConvexAuth();
|
|
58
|
-
const token = useAuthValue("token");
|
|
59
|
-
const onMutationUnauthorized = useAuthValue("onMutationUnauthorized");
|
|
60
|
-
let t0;
|
|
61
|
-
let t1;
|
|
62
|
-
if ($[0] !== authStore$1 || $[1] !== isAuthenticated || $[2] !== isLoading || $[3] !== onMutationUnauthorized || $[4] !== token) {
|
|
63
|
-
t0 = () => {
|
|
64
|
-
authStore$1.set("isLoading", isLoading);
|
|
65
|
-
authStore$1.set("isAuthenticated", isAuthenticated && !!token);
|
|
66
|
-
authStore$1.set("guard", (callback) => {
|
|
67
|
-
if (!token) {
|
|
68
|
-
onMutationUnauthorized();
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return callback ? void callback() : false;
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
t1 = [
|
|
75
|
-
isLoading,
|
|
76
|
-
isAuthenticated,
|
|
77
|
-
token,
|
|
78
|
-
onMutationUnauthorized,
|
|
79
|
-
authStore$1
|
|
80
|
-
];
|
|
81
|
-
$[0] = authStore$1;
|
|
82
|
-
$[1] = isAuthenticated;
|
|
83
|
-
$[2] = isLoading;
|
|
84
|
-
$[3] = onMutationUnauthorized;
|
|
85
|
-
$[4] = token;
|
|
86
|
-
$[5] = t0;
|
|
87
|
-
$[6] = t1;
|
|
88
|
-
} else {
|
|
89
|
-
t0 = $[5];
|
|
90
|
-
t1 = $[6];
|
|
48
|
+
/** Decode JWT expiration (ms timestamp) from token */
|
|
49
|
+
function decodeJwtExp(token) {
|
|
50
|
+
try {
|
|
51
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
52
|
+
return payload.exp ? payload.exp * 1e3 : null;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
91
55
|
}
|
|
92
|
-
React.useEffect(t0, t1);
|
|
93
|
-
return null;
|
|
94
56
|
}
|
|
95
|
-
const {
|
|
57
|
+
const { AuthProvider, useAuthStore, useAuthState, useAuthValue } = createAtomStore({
|
|
96
58
|
onMutationUnauthorized: () => {
|
|
97
59
|
throw new CRPCClientError({
|
|
98
60
|
code: "UNAUTHORIZED",
|
|
@@ -101,33 +63,30 @@ const { authStore, AuthProvider, useAuthStore, useAuthState, useAuthValue } = cr
|
|
|
101
63
|
},
|
|
102
64
|
onQueryUnauthorized: () => {},
|
|
103
65
|
isUnauthorized: defaultIsUnauthorized,
|
|
104
|
-
token:
|
|
105
|
-
|
|
106
|
-
isLoading:
|
|
107
|
-
isAuthenticated: false
|
|
108
|
-
guard: () => false
|
|
66
|
+
token: null,
|
|
67
|
+
expiresAt: null,
|
|
68
|
+
isLoading: true,
|
|
69
|
+
isAuthenticated: false
|
|
109
70
|
}, {
|
|
110
|
-
effect: AuthEffect,
|
|
111
71
|
name: "auth",
|
|
112
72
|
suppressWarnings: true
|
|
113
73
|
});
|
|
114
74
|
const useAuth = () => {
|
|
115
|
-
const authStore
|
|
116
|
-
if (!authStore
|
|
75
|
+
const authStore = useAuthStore();
|
|
76
|
+
if (!authStore.store) return {
|
|
117
77
|
hasSession: false,
|
|
118
78
|
isAuthenticated: false,
|
|
119
79
|
isLoading: false
|
|
120
80
|
};
|
|
121
81
|
if (typeof window === "undefined") return {
|
|
122
|
-
hasSession: !!authStore
|
|
82
|
+
hasSession: !!authStore.get("token"),
|
|
123
83
|
isAuthenticated: false,
|
|
124
84
|
isLoading: true
|
|
125
85
|
};
|
|
126
|
-
const {
|
|
127
|
-
const token = useAuthValue("token");
|
|
86
|
+
const { isLoading, isAuthenticated } = useConvexAuth();
|
|
128
87
|
return {
|
|
129
|
-
hasSession: !!token,
|
|
130
|
-
isAuthenticated
|
|
88
|
+
hasSession: !!useAuthValue("token"),
|
|
89
|
+
isAuthenticated,
|
|
131
90
|
isLoading
|
|
132
91
|
};
|
|
133
92
|
};
|
|
@@ -143,18 +102,18 @@ const useIsAuth = () => {
|
|
|
143
102
|
};
|
|
144
103
|
const useAuthGuard = () => {
|
|
145
104
|
const $ = c(3);
|
|
146
|
-
const
|
|
105
|
+
const { isAuthenticated } = useConvexAuth();
|
|
147
106
|
const onMutationUnauthorized = useAuthValue("onMutationUnauthorized");
|
|
148
107
|
let t0;
|
|
149
|
-
if ($[0] !==
|
|
108
|
+
if ($[0] !== isAuthenticated || $[1] !== onMutationUnauthorized) {
|
|
150
109
|
t0 = (callback) => {
|
|
151
|
-
if (!
|
|
110
|
+
if (!isAuthenticated) {
|
|
152
111
|
onMutationUnauthorized();
|
|
153
112
|
return true;
|
|
154
113
|
}
|
|
155
114
|
return callback ? void callback() : false;
|
|
156
115
|
};
|
|
157
|
-
$[0] =
|
|
116
|
+
$[0] = isAuthenticated;
|
|
158
117
|
$[1] = onMutationUnauthorized;
|
|
159
118
|
$[2] = t0;
|
|
160
119
|
} else t0 = $[2];
|
|
@@ -183,4 +142,4 @@ function Unauthenticated(t0) {
|
|
|
183
142
|
}
|
|
184
143
|
|
|
185
144
|
//#endregion
|
|
186
|
-
export { defaultIsUnauthorized as _,
|
|
145
|
+
export { defaultIsUnauthorized as _, MaybeUnauthenticated as a, useAuth as c, useAuthStore as d, useAuthValue as f, CRPCClientError as g, useMaybeAuth as h, MaybeAuthenticated as i, useAuthGuard as l, useIsAuth as m, Authenticated as n, Unauthenticated as o, useFetchAccessToken as p, FetchAccessTokenContext as r, decodeJwtExp as s, AuthProvider as t, useAuthState as u, isCRPCClientError as v };
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_codegen = require('./codegen-
|
|
2
|
+
const require_codegen = require('./codegen-Cca0gj6d.cjs');
|
|
3
3
|
let node_module = require("node:module");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_codegen.__toESM(node_path);
|
|
@@ -33,6 +33,8 @@ node_fs = __toESM(node_fs);
|
|
|
33
33
|
let jiti = require("jiti");
|
|
34
34
|
|
|
35
35
|
//#region src/cli/codegen.ts
|
|
36
|
+
/** Valid JS identifier pattern for object keys */
|
|
37
|
+
const VALID_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
36
38
|
function getConvexConfig(outputDir) {
|
|
37
39
|
const convexConfigPath = node_path.default.join(process.cwd(), "convex.json");
|
|
38
40
|
const functionsDir = (node_fs.default.existsSync(convexConfigPath) ? JSON.parse(node_fs.default.readFileSync(convexConfigPath, "utf-8")) : {}).functions || "convex";
|
|
@@ -42,12 +44,22 @@ function getConvexConfig(outputDir) {
|
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
47
|
+
* Check if a value is a CRPCHttpRouter (has _def.router === true)
|
|
48
|
+
*/
|
|
49
|
+
function isCRPCHttpRouter(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && "_def" in value && value._def?.router === true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
45
53
|
* Import a module using jiti and extract CRPC metadata from exports
|
|
46
54
|
*/
|
|
47
55
|
async function parseModuleRuntime(filePath, jiti$1) {
|
|
48
56
|
const result = {};
|
|
57
|
+
const httpRoutes = {};
|
|
49
58
|
const module$1 = await jiti$1.import(filePath);
|
|
50
|
-
if (!module$1 || typeof module$1 !== "object") return
|
|
59
|
+
if (!module$1 || typeof module$1 !== "object") return {
|
|
60
|
+
meta: null,
|
|
61
|
+
httpRoutes: {}
|
|
62
|
+
};
|
|
51
63
|
for (const [name, value] of Object.entries(module$1)) {
|
|
52
64
|
if (name.startsWith("_")) continue;
|
|
53
65
|
const meta = value?._crpcMeta;
|
|
@@ -58,8 +70,23 @@ async function parseModuleRuntime(filePath, jiti$1) {
|
|
|
58
70
|
for (const [key, val] of Object.entries(meta)) if (key !== "type" && key !== "internal" && val !== void 0) fnMeta[key] = val;
|
|
59
71
|
result[name] = fnMeta;
|
|
60
72
|
}
|
|
73
|
+
const httpRoute = value?._crpcHttpRoute;
|
|
74
|
+
if (httpRoute?.path && httpRoute?.method) httpRoutes[name] = {
|
|
75
|
+
path: httpRoute.path,
|
|
76
|
+
method: httpRoute.method
|
|
77
|
+
};
|
|
78
|
+
if (isCRPCHttpRouter(value)) for (const [procPath, procedure] of Object.entries(value._def.procedures)) {
|
|
79
|
+
const route = procedure._crpcHttpRoute;
|
|
80
|
+
if (route?.path && route?.method) httpRoutes[procPath] = {
|
|
81
|
+
path: route.path,
|
|
82
|
+
method: route.method
|
|
83
|
+
};
|
|
84
|
+
}
|
|
61
85
|
}
|
|
62
|
-
return
|
|
86
|
+
return {
|
|
87
|
+
meta: Object.keys(result).length > 0 ? result : null,
|
|
88
|
+
httpRoutes
|
|
89
|
+
};
|
|
63
90
|
}
|
|
64
91
|
async function generateMeta(outputDir, options) {
|
|
65
92
|
const startTime = Date.now();
|
|
@@ -72,24 +99,25 @@ async function generateMeta(outputDir, options) {
|
|
|
72
99
|
moduleCache: false
|
|
73
100
|
});
|
|
74
101
|
const meta = {};
|
|
102
|
+
const allHttpRoutes = {};
|
|
75
103
|
let totalFunctions = 0;
|
|
76
104
|
const files = node_fs.default.readdirSync(functionsDir).filter((file) => file.endsWith(".ts") && !file.startsWith("_") && ![
|
|
77
105
|
"schema.ts",
|
|
78
106
|
"convex.config.ts",
|
|
79
|
-
"auth.config.ts"
|
|
80
|
-
"http.ts"
|
|
107
|
+
"auth.config.ts"
|
|
81
108
|
].includes(file));
|
|
82
109
|
for (const file of files) {
|
|
83
110
|
const filePath = node_path.default.join(functionsDir, file);
|
|
84
111
|
const moduleName = file.replace(".ts", "");
|
|
85
112
|
try {
|
|
86
|
-
const moduleMeta = await parseModuleRuntime(filePath, jiti$1);
|
|
113
|
+
const { meta: moduleMeta, httpRoutes } = await parseModuleRuntime(filePath, jiti$1);
|
|
87
114
|
if (moduleMeta) {
|
|
88
115
|
meta[moduleName] = moduleMeta;
|
|
89
116
|
const fnCount = Object.keys(moduleMeta).length;
|
|
90
117
|
totalFunctions += fnCount;
|
|
91
118
|
if (debug) console.info(` ✓ ${moduleName}: ${fnCount} functions`);
|
|
92
119
|
}
|
|
120
|
+
Object.assign(allHttpRoutes, httpRoutes);
|
|
93
121
|
} catch (error) {
|
|
94
122
|
if (debug) console.error(` ⚠ Failed to parse ${file}:`, error);
|
|
95
123
|
}
|
|
@@ -106,11 +134,30 @@ async function generateMeta(outputDir, options) {
|
|
|
106
134
|
return ` ${fn}: ${`{ ${metaProps.join(", ")} }`}`;
|
|
107
135
|
}).join(",\n")},\n }`;
|
|
108
136
|
}).join(",\n");
|
|
137
|
+
const metaContent = metaEntries ? `\n${metaEntries},\n` : "";
|
|
138
|
+
const routesByPath = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const [key, route] of Object.entries(allHttpRoutes)) {
|
|
140
|
+
const pathKey = `${route.path}:${route.method}`;
|
|
141
|
+
const existing = routesByPath.get(pathKey) || [];
|
|
142
|
+
existing.push({
|
|
143
|
+
key,
|
|
144
|
+
route
|
|
145
|
+
});
|
|
146
|
+
routesByPath.set(pathKey, existing);
|
|
147
|
+
}
|
|
148
|
+
const dedupedRoutes = {};
|
|
149
|
+
for (const entries of routesByPath.values()) {
|
|
150
|
+
const best = entries.reduce((a, b) => a.key.length >= b.key.length ? a : b);
|
|
151
|
+
dedupedRoutes[best.key] = best.route;
|
|
152
|
+
}
|
|
153
|
+
const formatKey = (key) => VALID_IDENTIFIER_RE.test(key) ? key : `'${key}'`;
|
|
154
|
+
const httpEntries = Object.entries(dedupedRoutes).sort(([a], [b]) => a.localeCompare(b)).map(([name, route]) => ` ${formatKey(name)}: { path: '${route.path}', method: '${route.method}' }`).join(",\n");
|
|
155
|
+
const httpContent = httpEntries ? `\n${httpEntries},\n ` : "";
|
|
109
156
|
const output = `// biome-ignore-all format: generated
|
|
110
157
|
// This file is auto-generated by better-convex
|
|
111
158
|
// Do not edit manually. Run \`better-convex codegen\` to regenerate.
|
|
112
159
|
|
|
113
|
-
export const meta = {${
|
|
160
|
+
export const meta = {${metaContent ? `${metaContent} _http: {${httpContent}},\n` : `\n _http: {${httpContent}},\n`}} as const;
|
|
114
161
|
|
|
115
162
|
export type Meta = typeof meta;
|
|
116
163
|
`;
|
package/dist/crpc/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as HttpRouterRecord, a as HttpErrorCode, c as HttpRouteMap, d as isHttpClientError, h as CRPCHttpRouter, i as HttpClientOptions, l as InferHttpInput, n as HttpClientError, o as HttpProcedureCall, r as HttpClientFromRouter, s as HttpRouteInfo, t as HttpClient, u as InferHttpOutput } from "../http-types-BwEQgYV1.js";
|
|
2
|
+
import { C as PaginatedFnMeta, S as Meta, _ as ExtractPaginatedItem, a as ConvexInfiniteQueryMeta, b as InfiniteQueryInput, c as ConvexMutationKey, d as ConvexQueryMeta, f as ConvexQueryOptions, g as DecorateQuery, h as DecorateMutation, i as ConvexActionOptions, l as ConvexQueryHookOptions, m as DecorateInfiniteQuery, n as CRPCClient, o as ConvexInfiniteQueryOptions, p as DecorateAction, r as ConvexActionKey, s as ConvexInfiniteQueryOptionsWithRef, t as AuthType, u as ConvexQueryKey, v as FUNC_REF_SYMBOL, w as PaginationOpts, x as InfiniteQueryOptsParam, y as FnMeta } from "../types-DLVNrXKq.js";
|
|
2
3
|
import { FunctionArgs, FunctionReference } from "convex/server";
|
|
3
4
|
|
|
4
5
|
//#region src/crpc/error.d.ts
|
|
@@ -27,6 +28,44 @@ declare const isCRPCErrorCode: (error: unknown, code: ClientErrorCode) => error
|
|
|
27
28
|
/** Default unauthorized detection - checks UNAUTHORIZED code */
|
|
28
29
|
declare const defaultIsUnauthorized: (error: unknown) => boolean;
|
|
29
30
|
//#endregion
|
|
31
|
+
//#region src/crpc/http-client.d.ts
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a typed HTTP client for cRPC HTTP routers
|
|
35
|
+
*
|
|
36
|
+
* Supports nested router access like tRPC:
|
|
37
|
+
* - `http.todos.get({ id })` for nested routers
|
|
38
|
+
* - `http.health()` for flat procedures
|
|
39
|
+
*
|
|
40
|
+
* For React apps, prefer using `createCRPCContext` with HTTP options
|
|
41
|
+
* which provides TanStack Query integration (queryOptions, mutationOptions).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { createHttpClient } from 'better-convex/crpc';
|
|
46
|
+
* import { meta } from '../convex/shared/meta';
|
|
47
|
+
* import type { AppRouter } from '../convex/functions/http';
|
|
48
|
+
*
|
|
49
|
+
* const http = createHttpClient<AppRouter>({
|
|
50
|
+
* convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
|
|
51
|
+
* routes: meta._http!, // HTTP routes are in meta._http
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Nested access
|
|
55
|
+
* const todo = await http.todos.get({ id: 'abc123' });
|
|
56
|
+
*
|
|
57
|
+
* // Flat access
|
|
58
|
+
* const health = await http.health();
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function createHttpClient<TRouter extends CRPCHttpRouter<any>, TRoutes extends HttpRouteMap = HttpRouteMap>(opts: HttpClientOptions<TRoutes>): HttpClientFromRouter<TRouter>;
|
|
62
|
+
/**
|
|
63
|
+
* Create a typed HTTP client for flat HTTP router record
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Prefer passing a CRPCHttpRouter type for nested access
|
|
66
|
+
*/
|
|
67
|
+
declare function createHttpClient<TRouter extends HttpRouterRecord, TRoutes extends HttpRouteMap = HttpRouteMap>(opts: HttpClientOptions<TRoutes>): HttpClient<TRouter>;
|
|
68
|
+
//#endregion
|
|
30
69
|
//#region src/crpc/query-options.d.ts
|
|
31
70
|
/**
|
|
32
71
|
* Query options factory for Convex query function subscriptions.
|
|
@@ -67,4 +106,4 @@ declare function convexAction<T extends FunctionReference<'action'>>(funcRef: T,
|
|
|
67
106
|
*/
|
|
68
107
|
declare function convexInfiniteQueryOptions<T extends FunctionReference<'query'>>(funcRef: T, args: Record<string, unknown> | 'skip', opts?: InfiniteQueryOptsParam<T>, meta?: Meta): ConvexInfiniteQueryOptions<T>;
|
|
69
108
|
//#endregion
|
|
70
|
-
export { AuthType, CRPCClient, CRPCClientError, ConvexActionKey, ConvexActionOptions, ConvexInfiniteQueryMeta, ConvexInfiniteQueryOptions, ConvexInfiniteQueryOptionsWithRef, ConvexMutationKey, ConvexQueryHookOptions, ConvexQueryKey, ConvexQueryMeta, ConvexQueryOptions, DecorateAction, DecorateInfiniteQuery, DecorateMutation, DecorateQuery, ExtractPaginatedItem, FUNC_REF_SYMBOL, FnMeta, InfiniteQueryInput, InfiniteQueryOptsParam, Meta, PaginatedFnMeta, PaginationOpts, convexAction, convexInfiniteQueryOptions, convexQuery, defaultIsUnauthorized, isCRPCClientError, isCRPCErrorCode };
|
|
109
|
+
export { AuthType, CRPCClient, CRPCClientError, ConvexActionKey, ConvexActionOptions, ConvexInfiniteQueryMeta, ConvexInfiniteQueryOptions, ConvexInfiniteQueryOptionsWithRef, ConvexMutationKey, ConvexQueryHookOptions, ConvexQueryKey, ConvexQueryMeta, ConvexQueryOptions, DecorateAction, DecorateInfiniteQuery, DecorateMutation, DecorateQuery, ExtractPaginatedItem, FUNC_REF_SYMBOL, FnMeta, HttpClient, HttpClientError, HttpClientFromRouter, HttpClientOptions, HttpErrorCode, HttpProcedureCall, HttpRouteInfo, HttpRouteMap, InferHttpInput, InferHttpOutput, InfiniteQueryInput, InfiniteQueryOptsParam, Meta, PaginatedFnMeta, PaginationOpts, convexAction, convexInfiniteQueryOptions, convexQuery, createHttpClient, defaultIsUnauthorized, isCRPCClientError, isCRPCErrorCode, isHttpClientError };
|