kitcn 0.21.0 → 0.22.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 +1 -1
- package/dist/auth/client/index.js +1 -1
- package/dist/{auth-store-BHk8eMnX.js → auth-store-BnsWs1xd.js} +2 -1
- package/dist/{auth-store-47WTg13B.d.ts → auth-store-DHEk0ARa.d.ts} +10 -0
- package/dist/react/index.d.ts +7 -2
- package/dist/react/index.js +115 -40
- package/dist/solid/index.d.ts +51 -2
- package/dist/solid/index.js +419 -79
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { r as AuthStore } from "../../auth-store-
|
|
2
|
+
import { r as AuthStore } from "../../auth-store-DHEk0ARa.js";
|
|
3
3
|
import { ConvexReactClient } from "convex/react";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { A as CRPCClientError, C as decodeJwtExp, D as readAuthSessionFallbackToken, E as readAuthSessionFallbackData, O as writeAuthSessionFallbackData, T as clearAuthSessionFallback, d as isSessionSyncGraceActive, g as useAuthValue, h as useAuthStore, j as defaultIsUnauthorized, n as AuthProvider, o as ConvexProviderWithAuth$1, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS } from "../../auth-store-
|
|
2
|
+
import { A as CRPCClientError, C as decodeJwtExp, D as readAuthSessionFallbackToken, E as readAuthSessionFallbackData, O as writeAuthSessionFallbackData, T as clearAuthSessionFallback, d as isSessionSyncGraceActive, g as useAuthValue, h as useAuthStore, j as defaultIsUnauthorized, n as AuthProvider, o as ConvexProviderWithAuth$1, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS } from "../../auth-store-BnsWs1xd.js";
|
|
3
3
|
import { useConvexAuth } from "convex/react";
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -216,7 +216,8 @@ const { AuthProvider, useAuthStore, useAuthState, useAuthValue } = createAtomSto
|
|
|
216
216
|
expiresAt: null,
|
|
217
217
|
isLoading: true,
|
|
218
218
|
isAuthenticated: false,
|
|
219
|
-
sessionSyncGraceUntil: null
|
|
219
|
+
sessionSyncGraceUntil: null,
|
|
220
|
+
authEpoch: 0
|
|
220
221
|
}, {
|
|
221
222
|
name: "auth",
|
|
222
223
|
suppressWarnings: true
|
|
@@ -49,6 +49,12 @@ type AuthStoreState = {
|
|
|
49
49
|
isLoading: boolean; /** Auth state (synced from useConvexAuth for class methods) */
|
|
50
50
|
isAuthenticated: boolean; /** Grace window for freshly seeded auth tokens while session sync catches up */
|
|
51
51
|
sessionSyncGraceUntil: number | null;
|
|
52
|
+
/**
|
|
53
|
+
* Account generation, advanced on every identity transition. Client state
|
|
54
|
+
* that only makes sense inside one account's result set — paginated cursor
|
|
55
|
+
* chains — keys on it so it is rebuilt instead of reused across accounts.
|
|
56
|
+
*/
|
|
57
|
+
authEpoch: number;
|
|
52
58
|
};
|
|
53
59
|
declare const AUTH_SESSION_SYNC_GRACE_MS = 10000;
|
|
54
60
|
declare const isSessionSyncGraceActive: (sessionSyncGraceUntil: number | null) => boolean;
|
|
@@ -63,6 +69,7 @@ declare const AuthProvider: react.FC<jotai_x0.ProviderProps<{
|
|
|
63
69
|
isLoading: boolean;
|
|
64
70
|
isAuthenticated: boolean;
|
|
65
71
|
sessionSyncGraceUntil: number | null;
|
|
72
|
+
authEpoch: number;
|
|
66
73
|
}>>, useAuthStore: jotai_x0.UseStoreApi<AuthStoreState, object>, useAuthState: <K extends keyof AuthStoreState>(key: K, options?: string | jotai_x0.UseAtomOptions) => ({
|
|
67
74
|
onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>;
|
|
68
75
|
onQueryUnauthorized: jotai_x0.SimpleWritableAtom<(info: {
|
|
@@ -74,6 +81,7 @@ declare const AuthProvider: react.FC<jotai_x0.ProviderProps<{
|
|
|
74
81
|
isLoading: jotai_x0.SimpleWritableAtom<boolean>;
|
|
75
82
|
isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>;
|
|
76
83
|
sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>;
|
|
84
|
+
authEpoch: jotai_x0.SimpleWritableAtom<number>;
|
|
77
85
|
} & object)[K] extends jotai_vanilla0.WritableAtom<infer V, infer A extends unknown[], infer R> ? [V, (...args: A) => R] : never, useAuthValue: <K extends keyof AuthStoreState, S = (({
|
|
78
86
|
onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>;
|
|
79
87
|
onQueryUnauthorized: jotai_x0.SimpleWritableAtom<(info: {
|
|
@@ -85,6 +93,7 @@ declare const AuthProvider: react.FC<jotai_x0.ProviderProps<{
|
|
|
85
93
|
isLoading: jotai_x0.SimpleWritableAtom<boolean>;
|
|
86
94
|
isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>;
|
|
87
95
|
sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>;
|
|
96
|
+
authEpoch: jotai_x0.SimpleWritableAtom<number>;
|
|
88
97
|
} & object)[K] extends jotai_vanilla0.Atom<infer V> ? V : never)>(key: K, options?: ({
|
|
89
98
|
selector?: ((v: ({
|
|
90
99
|
onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>;
|
|
@@ -97,6 +106,7 @@ declare const AuthProvider: react.FC<jotai_x0.ProviderProps<{
|
|
|
97
106
|
isLoading: jotai_x0.SimpleWritableAtom<boolean>;
|
|
98
107
|
isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>;
|
|
99
108
|
sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>;
|
|
109
|
+
authEpoch: jotai_x0.SimpleWritableAtom<number>;
|
|
100
110
|
} & object)[K] extends jotai_vanilla0.Atom<infer V_1> ? V_1 : never, prevSelectorOutput?: S | undefined) => S) | undefined;
|
|
101
111
|
equalityFn?: ((prev: S, next: S) => boolean) | undefined;
|
|
102
112
|
} & jotai_x0.UseAtomOptions) | undefined, deps?: unknown[]) => S;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { A as decodeJwtExp, C as useAuthValue, D as useIsAuth, E as useFetchAccessToken, O as useMaybeAuth, S as useAuthStore, T as useConvexAuthRecovery, _ as Unauthenticated, a as Authenticated, b as useAuthGuard, c as ConvexAuthRecoveryError, d as ConvexAuthRecoveryStatus, f as ConvexProviderWithAuth, g as MaybeUnauthenticated, h as MaybeAuthenticated, i as AuthStoreState, k as useSafeConvexAuth, l as ConvexAuthRecoveryErrorCode, m as FetchAccessTokenFn, n as AuthProvider, o as ConvexAuthBridge, p as FetchAccessTokenContext, r as AuthStore, s as ConvexAuthRecovery, t as AUTH_SESSION_SYNC_GRACE_MS, u as ConvexAuthRecoveryOptions, v as isSessionSyncGraceActive, w as useConvexAuthBridge, x as useAuthState, y as useAuth } from "../auth-store-
|
|
2
|
+
import { A as decodeJwtExp, C as useAuthValue, D as useIsAuth, E as useFetchAccessToken, O as useMaybeAuth, S as useAuthStore, T as useConvexAuthRecovery, _ as Unauthenticated, a as Authenticated, b as useAuthGuard, c as ConvexAuthRecoveryError, d as ConvexAuthRecoveryStatus, f as ConvexProviderWithAuth, g as MaybeUnauthenticated, h as MaybeAuthenticated, i as AuthStoreState, k as useSafeConvexAuth, l as ConvexAuthRecoveryErrorCode, m as FetchAccessTokenFn, n as AuthProvider, o as ConvexAuthBridge, p as FetchAccessTokenContext, r as AuthStore, s as ConvexAuthRecovery, t as AUTH_SESSION_SYNC_GRACE_MS, u as ConvexAuthRecoveryOptions, v as isSessionSyncGraceActive, w as useConvexAuthBridge, x as useAuthState, y as useAuth } from "../auth-store-DHEk0ARa.js";
|
|
3
3
|
import { ConvexProvider, ConvexReactClient, ConvexReactClient as ConvexReactClient$1, ConvexReactClientOptions, Watch, WatchQueryOptions, useConvex } from "convex/react";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
@@ -235,7 +235,6 @@ declare class ConvexQueryClient {
|
|
|
235
235
|
private cancelPendingUnsubscribe;
|
|
236
236
|
/** Unsubscribe a live Convex watch (if present) and remove it from the subscription map. */
|
|
237
237
|
private unsubscribeQueryByHash;
|
|
238
|
-
private isAuthBoundQuery;
|
|
239
238
|
private subscribeQuery;
|
|
240
239
|
/** Update auth store (for HMR where jotai store may reset) */
|
|
241
240
|
updateAuthStore(authStore?: AuthStore): void;
|
|
@@ -271,6 +270,12 @@ declare class ConvexQueryClient {
|
|
|
271
270
|
* Call before logout to prevent UNAUTHORIZED errors during session invalidation.
|
|
272
271
|
*/
|
|
273
272
|
unsubscribeAuthQueries(): void;
|
|
273
|
+
/**
|
|
274
|
+
* Advance the account generation published by the auth store.
|
|
275
|
+
* Client state that only makes sense inside one account's result set —
|
|
276
|
+
* paginated cursor chains — keys on it, so it is rebuilt instead of reused.
|
|
277
|
+
*/
|
|
278
|
+
private bumpAuthEpoch;
|
|
274
279
|
resetAuthQueries(): Promise<void>;
|
|
275
280
|
/**
|
|
276
281
|
* Batch update all subscriptions.
|
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { A as CRPCClientError, C as decodeJwtExp, M as isCRPCClientError, O as writeAuthSessionFallbackData, S as useSafeConvexAuth, T as clearAuthSessionFallback, _ as useConvexAuthBridge, a as ConvexAuthRecoveryError, b as useIsAuth, c as MaybeAuthenticated, d as isSessionSyncGraceActive, f as useAuth, g as useAuthValue, h as useAuthStore, i as ConvexAuthBridge, j as defaultIsUnauthorized, k as writeAuthSessionFallbackToken, l as MaybeUnauthenticated, m as useAuthState, n as AuthProvider, o as ConvexProviderWithAuth, p as useAuthGuard, r as Authenticated, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS, u as Unauthenticated, v as useConvexAuthRecovery, w as decodeJwtIdentity, x as useMaybeAuth, y as useFetchAccessToken } from "../auth-store-
|
|
2
|
+
import { A as CRPCClientError, C as decodeJwtExp, M as isCRPCClientError, O as writeAuthSessionFallbackData, S as useSafeConvexAuth, T as clearAuthSessionFallback, _ as useConvexAuthBridge, a as ConvexAuthRecoveryError, b as useIsAuth, c as MaybeAuthenticated, d as isSessionSyncGraceActive, f as useAuth, g as useAuthValue, h as useAuthStore, i as ConvexAuthBridge, j as defaultIsUnauthorized, k as writeAuthSessionFallbackToken, l as MaybeUnauthenticated, m as useAuthState, n as AuthProvider, o as ConvexProviderWithAuth, p as useAuthGuard, r as Authenticated, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS, u as Unauthenticated, v as useConvexAuthRecovery, w as decodeJwtIdentity, x as useMaybeAuth, y as useFetchAccessToken } from "../auth-store-BnsWs1xd.js";
|
|
3
3
|
import { ConvexProvider, ConvexReactClient, ConvexReactClient as ConvexReactClient$1, useAction, useConvex, useMutation } from "convex/react";
|
|
4
4
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1647,7 +1647,6 @@ function createAuthMutations(authClient) {
|
|
|
1647
1647
|
});
|
|
1648
1648
|
const useSignInSocialMutationOptions = ((options) => {
|
|
1649
1649
|
const authStoreApi = useAuthStore();
|
|
1650
|
-
const convexQueryClient = useConvexQueryClient();
|
|
1651
1650
|
return {
|
|
1652
1651
|
...options,
|
|
1653
1652
|
mutationFn: async (args) => {
|
|
@@ -1659,14 +1658,12 @@ function createAuthMutations(authClient) {
|
|
|
1659
1658
|
await hydrateReturnedSession(authClient, res);
|
|
1660
1659
|
await ensureAuth(authStoreApi);
|
|
1661
1660
|
authStoreApi.set("isAuthenticated", true);
|
|
1662
|
-
await convexQueryClient?.resetAuthQueries();
|
|
1663
1661
|
return res;
|
|
1664
1662
|
}
|
|
1665
1663
|
};
|
|
1666
1664
|
});
|
|
1667
1665
|
const useSignInMutationOptions = ((options) => {
|
|
1668
1666
|
const authStoreApi = useAuthStore();
|
|
1669
|
-
const convexQueryClient = useConvexQueryClient();
|
|
1670
1667
|
const { signInMethod = "email", ...mutationOptions } = options ?? {};
|
|
1671
1668
|
return {
|
|
1672
1669
|
...mutationOptions,
|
|
@@ -1679,14 +1676,12 @@ function createAuthMutations(authClient) {
|
|
|
1679
1676
|
await hydrateReturnedSession(authClient, res);
|
|
1680
1677
|
await ensureAuth(authStoreApi);
|
|
1681
1678
|
authStoreApi.set("isAuthenticated", true);
|
|
1682
|
-
await convexQueryClient?.resetAuthQueries();
|
|
1683
1679
|
return res;
|
|
1684
1680
|
}
|
|
1685
1681
|
};
|
|
1686
1682
|
});
|
|
1687
1683
|
const useSignUpMutationOptions = ((options) => {
|
|
1688
1684
|
const authStoreApi = useAuthStore();
|
|
1689
|
-
const convexQueryClient = useConvexQueryClient();
|
|
1690
1685
|
return {
|
|
1691
1686
|
...options,
|
|
1692
1687
|
mutationFn: async (args) => {
|
|
@@ -1698,7 +1693,6 @@ function createAuthMutations(authClient) {
|
|
|
1698
1693
|
await hydrateReturnedSession(authClient, res);
|
|
1699
1694
|
await ensureAuth(authStoreApi);
|
|
1700
1695
|
authStoreApi.set("isAuthenticated", true);
|
|
1701
|
-
await convexQueryClient?.resetAuthQueries();
|
|
1702
1696
|
return res;
|
|
1703
1697
|
}
|
|
1704
1698
|
};
|
|
@@ -1711,6 +1705,81 @@ function createAuthMutations(authClient) {
|
|
|
1711
1705
|
};
|
|
1712
1706
|
}
|
|
1713
1707
|
|
|
1708
|
+
//#endregion
|
|
1709
|
+
//#region src/internal/auth-reset.ts
|
|
1710
|
+
/**
|
|
1711
|
+
* Erase the cached result of every auth-bound query.
|
|
1712
|
+
*
|
|
1713
|
+
* `queryClient.resetQueries()` is not enough. It restores each query's
|
|
1714
|
+
* `initialState`, and query-core derives that from `initialData` when the query
|
|
1715
|
+
* is built, never re-deriving it once real data lands. An auth-bound entry
|
|
1716
|
+
* therefore comes back holding the previous account's rows marked `success`,
|
|
1717
|
+
* and nothing corrects it: Convex query options set `staleTime: Infinity` with
|
|
1718
|
+
* every refetch trigger off, and `resetQueries` only refetches entries that are
|
|
1719
|
+
* currently active.
|
|
1720
|
+
*
|
|
1721
|
+
* Every entry is removed so query-core forgets both its current state and its
|
|
1722
|
+
* private `initialState`. Mounted observers are rebound with `initialData`
|
|
1723
|
+
* removed, creating a pristine replacement query that future public resets
|
|
1724
|
+
* cannot use to resurrect the previous account's rows.
|
|
1725
|
+
*/
|
|
1726
|
+
async function clearAuthBoundQueries(cache, isAuthBound) {
|
|
1727
|
+
const authQueries = cache.getAll().filter((query) => isAuthBound(query));
|
|
1728
|
+
const restoreObservers = [];
|
|
1729
|
+
await Promise.all(authQueries.map((query) => query.cancel({ silent: true })));
|
|
1730
|
+
for (const query of authQueries) {
|
|
1731
|
+
const observers = [...query.observers];
|
|
1732
|
+
cache.remove(query);
|
|
1733
|
+
for (const observer of observers) {
|
|
1734
|
+
const previousOptions = observer.options;
|
|
1735
|
+
observer.setOptions({
|
|
1736
|
+
...observer.options,
|
|
1737
|
+
enabled: false,
|
|
1738
|
+
initialData: void 0,
|
|
1739
|
+
placeholderData: void 0
|
|
1740
|
+
});
|
|
1741
|
+
const suspendedOptions = observer.options;
|
|
1742
|
+
restoreObservers.push(() => {
|
|
1743
|
+
const currentOptions = observer.options;
|
|
1744
|
+
observer.setOptions({
|
|
1745
|
+
...currentOptions,
|
|
1746
|
+
enabled: observer.options === suspendedOptions ? previousOptions.enabled : currentOptions.enabled,
|
|
1747
|
+
initialData: void 0,
|
|
1748
|
+
placeholderData: void 0
|
|
1749
|
+
});
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return () => {
|
|
1754
|
+
for (const restoreObserver of restoreObservers) restoreObserver();
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
//#endregion
|
|
1759
|
+
//#region src/internal/subscription-gate.ts
|
|
1760
|
+
/** Read kitcn's meta off a TanStack query. */
|
|
1761
|
+
function readConvexQueryMeta(query) {
|
|
1762
|
+
return query.meta;
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Auth-bound queries are cleared and resubscribed on an identity transition so
|
|
1766
|
+
* one account never renders another account's cached rows.
|
|
1767
|
+
*/
|
|
1768
|
+
function isAuthBoundQuery(query) {
|
|
1769
|
+
const authType = readConvexQueryMeta(query)?.authType;
|
|
1770
|
+
return authType === "required" || authType === "optional";
|
|
1771
|
+
}
|
|
1772
|
+
/** Whether a Convex subscription may be opened for this query. */
|
|
1773
|
+
function canSubscribeQuery(query, opts) {
|
|
1774
|
+
if (opts.isSubscribed) return false;
|
|
1775
|
+
const meta = readConvexQueryMeta(query);
|
|
1776
|
+
if (meta?.subscribe === false) return false;
|
|
1777
|
+
if (query.getObserversCount() === 0) return false;
|
|
1778
|
+
if (query.isDisabled()) return false;
|
|
1779
|
+
if (opts.shouldSkipSubscription(meta?.authType)) return false;
|
|
1780
|
+
return true;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1714
1783
|
//#endregion
|
|
1715
1784
|
//#region src/react/client.ts
|
|
1716
1785
|
/**
|
|
@@ -1882,17 +1951,11 @@ var ConvexQueryClient = class {
|
|
|
1882
1951
|
sub.unsubscribe();
|
|
1883
1952
|
delete this.subscriptions[queryHash];
|
|
1884
1953
|
}
|
|
1885
|
-
isAuthBoundQuery(query) {
|
|
1886
|
-
const meta = query.meta;
|
|
1887
|
-
return meta?.authType === "required" || meta?.authType === "optional";
|
|
1888
|
-
}
|
|
1889
1954
|
subscribeQuery(query) {
|
|
1890
|
-
if (
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
if (query.isDisabled()) return;
|
|
1895
|
-
if (this.shouldSkipSubscription(meta?.authType)) return;
|
|
1955
|
+
if (!canSubscribeQuery(query, {
|
|
1956
|
+
isSubscribed: !!this.subscriptions[query.queryHash],
|
|
1957
|
+
shouldSkipSubscription: (authType) => this.shouldSkipSubscription(authType)
|
|
1958
|
+
})) return;
|
|
1896
1959
|
const [, funcName, args] = query.queryKey;
|
|
1897
1960
|
const watch = this.convexClient.watchQuery(funcName, this.transformer.input.serialize(args));
|
|
1898
1961
|
const unsubscribe = watch.onUpdate(() => {
|
|
@@ -1986,14 +2049,29 @@ var ConvexQueryClient = class {
|
|
|
1986
2049
|
this.unsubscribeQueryByHash(queryHash);
|
|
1987
2050
|
}
|
|
1988
2051
|
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Advance the account generation published by the auth store.
|
|
2054
|
+
* Client state that only makes sense inside one account's result set —
|
|
2055
|
+
* paginated cursor chains — keys on it, so it is rebuilt instead of reused.
|
|
2056
|
+
*/
|
|
2057
|
+
bumpAuthEpoch() {
|
|
2058
|
+
if (!this.authStore) return;
|
|
2059
|
+
this.authStore.set("authEpoch", this.authStore.get("authEpoch") + 1);
|
|
2060
|
+
}
|
|
1989
2061
|
async resetAuthQueries() {
|
|
1990
|
-
const
|
|
1991
|
-
for (const query of
|
|
2062
|
+
const queryCache = this.queryClient.getQueryCache();
|
|
2063
|
+
for (const query of queryCache.getAll()) {
|
|
2064
|
+
if (!isAuthBoundQuery(query)) continue;
|
|
1992
2065
|
this.cancelPendingUnsubscribe(query.queryHash);
|
|
1993
2066
|
this.unsubscribeQueryByHash(query.queryHash);
|
|
1994
2067
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
2068
|
+
this.bumpAuthEpoch();
|
|
2069
|
+
(await clearAuthBoundQueries(queryCache, (query) => isAuthBoundQuery(query)))();
|
|
2070
|
+
await this.queryClient.refetchQueries({
|
|
2071
|
+
predicate: (query) => isAuthBoundQuery(query),
|
|
2072
|
+
type: "active"
|
|
2073
|
+
});
|
|
2074
|
+
for (const query of queryCache.getAll()) if (isAuthBoundQuery(query)) this.subscribeQuery(query);
|
|
1997
2075
|
}
|
|
1998
2076
|
/**
|
|
1999
2077
|
* Batch update all subscriptions.
|
|
@@ -2270,15 +2348,8 @@ const shouldSplitPaginationPage = (page, initialNumItems) => Boolean(page.splitC
|
|
|
2270
2348
|
//#region src/react/use-infinite-query.ts
|
|
2271
2349
|
/** biome-ignore-all lint/suspicious/noExplicitAny: Convex query/mutation type compatibility */
|
|
2272
2350
|
const PAGINATION_KEY_PREFIX = "__pagination__";
|
|
2273
|
-
const paginationIdStore = /* @__PURE__ */ new Map();
|
|
2274
2351
|
let paginationIdCounter = 0;
|
|
2275
|
-
const
|
|
2276
|
-
const existing = paginationIdStore.get(storeKey);
|
|
2277
|
-
if (existing !== void 0) return existing;
|
|
2278
|
-
const newId = ++paginationIdCounter;
|
|
2279
|
-
paginationIdStore.set(storeKey, newId);
|
|
2280
|
-
return newId;
|
|
2281
|
-
};
|
|
2352
|
+
const createPaginationId = () => ++paginationIdCounter;
|
|
2282
2353
|
/** Build a unique key for recovery attempt detection */
|
|
2283
2354
|
const buildRecoveryKey = (pageKeys, page0Cursor, page0UpdatedAt) => JSON.stringify({
|
|
2284
2355
|
pageKeys,
|
|
@@ -2360,12 +2431,13 @@ const useStaleCursorRecovery = ({ argsObject, combined, limit, setState, state }
|
|
|
2360
2431
|
* Use `useInfiniteQuery` for the public API with auth handling.
|
|
2361
2432
|
*/
|
|
2362
2433
|
const useInfiniteQueryInternal = (query, args, options) => {
|
|
2363
|
-
const { limit, enabled, placeholderData, ...forwardedOptions } = options;
|
|
2434
|
+
const { limit, authType, enabled, placeholderData, ...forwardedOptions } = options;
|
|
2364
2435
|
const queryOptions = useStableIdentity(forwardedOptions);
|
|
2365
2436
|
const { isLoading: isAuthLoading } = useSafeConvexAuth();
|
|
2366
2437
|
const meta = useMeta();
|
|
2367
2438
|
const queryClient = useQueryClient();
|
|
2368
|
-
const
|
|
2439
|
+
const authEpoch = useAuthValue("authEpoch");
|
|
2440
|
+
const skip = !useMemo(() => {
|
|
2369
2441
|
const serverQueryKey = [
|
|
2370
2442
|
"convexQuery",
|
|
2371
2443
|
getFunctionName(query),
|
|
@@ -2381,8 +2453,7 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2381
2453
|
JSON.stringify(args),
|
|
2382
2454
|
limit,
|
|
2383
2455
|
queryClient
|
|
2384
|
-
]);
|
|
2385
|
-
const skip = !prefetchedFirstPage && (isAuthLoading || enabled === false);
|
|
2456
|
+
]) && (isAuthLoading || enabled === false);
|
|
2386
2457
|
const getPaginationState = useCallback((key) => {
|
|
2387
2458
|
const queryKey = [PAGINATION_KEY_PREFIX, key];
|
|
2388
2459
|
return queryClient.getQueryData(queryKey);
|
|
@@ -2394,10 +2465,16 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2394
2465
|
const argsObject = useMemo(() => skip ? {} : args, [skip, JSON.stringify(args)]);
|
|
2395
2466
|
const storeKey = useMemo(() => JSON.stringify({
|
|
2396
2467
|
query: getFunctionName(query),
|
|
2397
|
-
args: argsObject
|
|
2398
|
-
|
|
2468
|
+
args: argsObject,
|
|
2469
|
+
...authType ? { authEpoch } : {}
|
|
2470
|
+
}), [
|
|
2471
|
+
query,
|
|
2472
|
+
argsObject,
|
|
2473
|
+
authType,
|
|
2474
|
+
authEpoch
|
|
2475
|
+
]);
|
|
2399
2476
|
const createInitialState = useCallback(() => {
|
|
2400
|
-
const id =
|
|
2477
|
+
const id = createPaginationId();
|
|
2401
2478
|
return {
|
|
2402
2479
|
id,
|
|
2403
2480
|
nextPageKey: 1,
|
|
@@ -2411,7 +2488,6 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2411
2488
|
version: 0
|
|
2412
2489
|
};
|
|
2413
2490
|
}, [
|
|
2414
|
-
storeKey,
|
|
2415
2491
|
skip,
|
|
2416
2492
|
argsObject,
|
|
2417
2493
|
limit
|
|
@@ -2484,10 +2560,9 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2484
2560
|
const pageArgs = state.queries[key]?.args;
|
|
2485
2561
|
return {
|
|
2486
2562
|
...convexQuery(query, pageArgs ? (({ __paginationId, ...rest }) => rest)(pageArgs) : "skip", meta),
|
|
2487
|
-
enabled: resolveEnabled(!skip && !!state.queries[key], enabled),
|
|
2563
|
+
enabled: resolveEnabled(!skip && !!state.queries[key] && (!authType || !isAuthLoading), enabled),
|
|
2488
2564
|
structuralSharing: false,
|
|
2489
2565
|
...queryOptions ?? {},
|
|
2490
|
-
...index === 0 && prefetchedFirstPage ? { initialData: prefetchedFirstPage } : {},
|
|
2491
2566
|
...index === 0 && placeholderData ? { placeholderData: {
|
|
2492
2567
|
page: placeholderData,
|
|
2493
2568
|
isDone: false,
|
|
@@ -2502,7 +2577,6 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2502
2577
|
enabled,
|
|
2503
2578
|
meta,
|
|
2504
2579
|
queryOptions,
|
|
2505
|
-
prefetchedFirstPage,
|
|
2506
2580
|
placeholderData
|
|
2507
2581
|
]),
|
|
2508
2582
|
combine: useCallback((results) => {
|
|
@@ -2689,6 +2763,7 @@ function useInfiniteQuery(infiniteOptions) {
|
|
|
2689
2763
|
]);
|
|
2690
2764
|
const enabled = useMemo(() => resolveEnabled(!shouldSkip, factoryEnabled), [shouldSkip, factoryEnabled]);
|
|
2691
2765
|
const result = useInfiniteQueryInternal(query, args, {
|
|
2766
|
+
authType,
|
|
2692
2767
|
limit,
|
|
2693
2768
|
...queryOptions,
|
|
2694
2769
|
enabled
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -210,11 +210,15 @@ declare const FetchAccessTokenContext: solid_js0.Context<FetchAccessTokenFn | nu
|
|
|
210
210
|
/** Get fetchAccessToken from context (available immediately, no race condition) */
|
|
211
211
|
declare const useFetchAccessToken: () => FetchAccessTokenFn | null;
|
|
212
212
|
type ConvexAuthResult = {
|
|
213
|
+
identity: unknown;
|
|
213
214
|
isAuthenticated: boolean;
|
|
214
215
|
isLoading: boolean;
|
|
215
216
|
};
|
|
217
|
+
type ConvexAuthBridgeResult = ConvexAuthResult & {
|
|
218
|
+
authEpoch: number;
|
|
219
|
+
};
|
|
216
220
|
/** Get auth from bridge context (null if no bridge configured) */
|
|
217
|
-
declare const useConvexAuthBridge: () =>
|
|
221
|
+
declare const useConvexAuthBridge: () => ConvexAuthBridgeResult | null;
|
|
218
222
|
type AuthStoreState = {
|
|
219
223
|
/** Callback when mutation/action called while unauthorized. Throws by default. */onMutationUnauthorized: () => void; /** Callback when query called while unauthorized. Noop by default. */
|
|
220
224
|
onQueryUnauthorized: (info: {
|
|
@@ -225,6 +229,12 @@ type AuthStoreState = {
|
|
|
225
229
|
expiresAt: number | null; /** Auth loading state (synced from useConvexAuth for class methods) */
|
|
226
230
|
isLoading: boolean; /** Auth state (synced from useConvexAuth for class methods) */
|
|
227
231
|
isAuthenticated: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Account generation, advanced on every identity transition. Client state
|
|
234
|
+
* that only makes sense inside one account's result set — paginated cursor
|
|
235
|
+
* chains — keys on it so it is rebuilt instead of reused across accounts.
|
|
236
|
+
*/
|
|
237
|
+
authEpoch: number;
|
|
228
238
|
};
|
|
229
239
|
/** Decode JWT expiration (ms timestamp) from token */
|
|
230
240
|
declare function decodeJwtExp(token: string): number | null;
|
|
@@ -258,6 +268,8 @@ declare function useSafeConvexAuth(): ConvexAuthResult;
|
|
|
258
268
|
* @internal
|
|
259
269
|
*/
|
|
260
270
|
declare function ConvexAuthBridge(props: ParentProps<{
|
|
271
|
+
authEpoch?: number;
|
|
272
|
+
identity?: unknown;
|
|
261
273
|
isLoading: boolean;
|
|
262
274
|
isAuthenticated: boolean;
|
|
263
275
|
}>): JSX.Element;
|
|
@@ -369,6 +381,12 @@ declare class ConvexQueryClient {
|
|
|
369
381
|
ssrQueryMode: 'consistent' | 'inconsistent';
|
|
370
382
|
/** Auth store for checking auth state */
|
|
371
383
|
private authStore?;
|
|
384
|
+
/** In-flight clear shared by overlapping account identity transitions. */
|
|
385
|
+
private authBarrierClear?;
|
|
386
|
+
/** Blocks auth-bound work created while Convex is changing identity. */
|
|
387
|
+
private authSettlementBarrier?;
|
|
388
|
+
/** Latest account transition allowed to restore and refetch observers. */
|
|
389
|
+
private authResetGeneration;
|
|
372
390
|
/** Delay before unsubscribing when query has no observers */
|
|
373
391
|
private unsubscribeDelay;
|
|
374
392
|
/** Payload transformer used across request/response boundaries. */
|
|
@@ -381,6 +399,11 @@ declare class ConvexQueryClient {
|
|
|
381
399
|
private cancelPendingUnsubscribe;
|
|
382
400
|
/** Unsubscribe a live Convex subscription (if present) and remove it from the subscription map. */
|
|
383
401
|
private unsubscribeQueryByHash;
|
|
402
|
+
/**
|
|
403
|
+
* Open a Convex subscription for a query, if the shared gate allows it.
|
|
404
|
+
* Single owner of the subscribe preconditions for every cache-event branch.
|
|
405
|
+
*/
|
|
406
|
+
private subscribeQuery;
|
|
384
407
|
/** Update auth store (for HMR where store may reset) */
|
|
385
408
|
updateAuthStore(authStore?: AuthStore): void;
|
|
386
409
|
/** Get current auth state from store */
|
|
@@ -390,6 +413,10 @@ declare class ConvexQueryClient {
|
|
|
390
413
|
* Needed for useSuspenseQuery which ignores enabled: false.
|
|
391
414
|
*/
|
|
392
415
|
private shouldSkipSubscription;
|
|
416
|
+
/** Hold new auth-bound requests until the latest Convex identity settles. */
|
|
417
|
+
private beginAuthSettlementBarrier;
|
|
418
|
+
/** Wait through overlapping barriers and stop canceled requests. */
|
|
419
|
+
private waitForAuthSettlement;
|
|
393
420
|
/** Get QueryClient, throwing if not connected */
|
|
394
421
|
get queryClient(): QueryClient;
|
|
395
422
|
/**
|
|
@@ -414,6 +441,25 @@ declare class ConvexQueryClient {
|
|
|
414
441
|
* Call before logout to prevent UNAUTHORIZED errors during session invalidation.
|
|
415
442
|
*/
|
|
416
443
|
unsubscribeAuthQueries(): void;
|
|
444
|
+
/**
|
|
445
|
+
* Advance the account generation published by the auth store.
|
|
446
|
+
* Client state that only makes sense inside one account's result set —
|
|
447
|
+
* paginated cursor chains — keys on it, so it is rebuilt instead of reused.
|
|
448
|
+
*/
|
|
449
|
+
private bumpAuthEpoch;
|
|
450
|
+
/** Drop auth-bound data without fetching while Convex changes identity. */
|
|
451
|
+
private clearAuthQueries;
|
|
452
|
+
/** Refetch and resubscribe auth-bound queries after Convex settles. */
|
|
453
|
+
private refetchAuthQueries;
|
|
454
|
+
/**
|
|
455
|
+
* Drop every auth-bound cache entry and resubscribe.
|
|
456
|
+
* Call after an identity transition when no separate settlement barrier owns
|
|
457
|
+
* the clear/refetch phases.
|
|
458
|
+
*/
|
|
459
|
+
resetAuthQueries(options?: {
|
|
460
|
+
generation?: number;
|
|
461
|
+
refetch?: boolean;
|
|
462
|
+
}): Promise<number>;
|
|
417
463
|
/**
|
|
418
464
|
* Batch update all subscriptions.
|
|
419
465
|
* Called internally when Convex client reconnects.
|
|
@@ -1271,6 +1317,7 @@ declare function ConvexProvider(props: ParentProps<{
|
|
|
1271
1317
|
declare function ConvexProviderWithAuth(props: ParentProps<{
|
|
1272
1318
|
client: ConvexClient;
|
|
1273
1319
|
useAuth: () => {
|
|
1320
|
+
/** Stable account/session identity. Token refreshes must keep this value. */identity?: string | null;
|
|
1274
1321
|
isLoading: boolean;
|
|
1275
1322
|
isAuthenticated: boolean;
|
|
1276
1323
|
fetchAccessToken: AuthTokenFetcher;
|
|
@@ -1477,7 +1524,9 @@ declare function useConvexInfiniteQueryOptions<T extends FunctionReference<'quer
|
|
|
1477
1524
|
*/
|
|
1478
1525
|
declare function useConvexActionQueryOptions<Action extends FunctionReference<'action'>>(action: Action, args: FunctionArgs<Action> | SkipToken, options?: {
|
|
1479
1526
|
skipUnauth?: boolean;
|
|
1480
|
-
} & DistributiveOmit<SolidQueryOptions<FunctionReturnType<Action>, DefaultError>, ReservedQueryOptions>): ConvexActionOptions<Action
|
|
1527
|
+
} & DistributiveOmit<SolidQueryOptions<FunctionReturnType<Action>, DefaultError>, ReservedQueryOptions>): ConvexActionOptions<Action> & {
|
|
1528
|
+
meta: ConvexQueryMeta;
|
|
1529
|
+
};
|
|
1481
1530
|
/**
|
|
1482
1531
|
* Hook that returns mutation options for use with useMutation.
|
|
1483
1532
|
* Wraps the Convex mutation with auth guard logic.
|
package/dist/solid/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getFunctionName } from "convex/server";
|
|
2
|
-
import { Show, createComputed, createContext, createEffect, createMemo, createRenderEffect, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
|
|
2
|
+
import { Show, batch, createComputed, createContext, createEffect, createMemo, createRenderEffect, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
|
|
3
3
|
import { createComponent, memo } from "solid-js/web";
|
|
4
4
|
import { createStore } from "solid-js/store";
|
|
5
5
|
import { notifyManager, skipToken, useQueryClient } from "@tanstack/solid-query";
|
|
@@ -78,7 +78,8 @@ const defaultState = {
|
|
|
78
78
|
token: null,
|
|
79
79
|
expiresAt: null,
|
|
80
80
|
isLoading: true,
|
|
81
|
-
isAuthenticated: false
|
|
81
|
+
isAuthenticated: false,
|
|
82
|
+
authEpoch: 0
|
|
82
83
|
};
|
|
83
84
|
const AuthStoreContext = createContext(null);
|
|
84
85
|
function useAuthStore() {
|
|
@@ -91,7 +92,14 @@ function useAuthStore() {
|
|
|
91
92
|
return ctx;
|
|
92
93
|
}
|
|
93
94
|
function useAuthValue(key) {
|
|
94
|
-
|
|
95
|
+
const store = useAuthStore();
|
|
96
|
+
const bridgeAuth = useConvexAuthBridge();
|
|
97
|
+
if (!store.store && bridgeAuth) {
|
|
98
|
+
if (key === "authEpoch") return bridgeAuth.authEpoch;
|
|
99
|
+
if (key === "isAuthenticated") return bridgeAuth.isAuthenticated;
|
|
100
|
+
if (key === "isLoading") return bridgeAuth.isLoading;
|
|
101
|
+
}
|
|
102
|
+
return store.get(key);
|
|
95
103
|
}
|
|
96
104
|
function AuthProvider(props) {
|
|
97
105
|
const [state, setState] = createStore({
|
|
@@ -125,14 +133,17 @@ function useSafeConvexAuth() {
|
|
|
125
133
|
const authStore = useAuthStore();
|
|
126
134
|
const bridgeAuth = useConvexAuthBridge();
|
|
127
135
|
return {
|
|
136
|
+
get identity() {
|
|
137
|
+
return bridgeAuth?.identity ?? null;
|
|
138
|
+
},
|
|
128
139
|
get isAuthenticated() {
|
|
129
|
-
if (authStore.store) return authStore.get("isAuthenticated");
|
|
130
140
|
if (bridgeAuth !== null) return bridgeAuth.isAuthenticated;
|
|
141
|
+
if (authStore.store) return authStore.get("isAuthenticated");
|
|
131
142
|
return false;
|
|
132
143
|
},
|
|
133
144
|
get isLoading() {
|
|
134
|
-
if (authStore.store) return authStore.get("isLoading");
|
|
135
145
|
if (bridgeAuth !== null) return bridgeAuth.isLoading;
|
|
146
|
+
if (authStore.store) return authStore.get("isLoading");
|
|
136
147
|
return false;
|
|
137
148
|
}
|
|
138
149
|
};
|
|
@@ -144,6 +155,12 @@ function useSafeConvexAuth() {
|
|
|
144
155
|
function ConvexAuthBridge(props) {
|
|
145
156
|
return createComponent(ConvexAuthBridgeContext.Provider, {
|
|
146
157
|
value: {
|
|
158
|
+
get authEpoch() {
|
|
159
|
+
return props.authEpoch ?? 0;
|
|
160
|
+
},
|
|
161
|
+
get identity() {
|
|
162
|
+
return props.identity ?? null;
|
|
163
|
+
},
|
|
147
164
|
get isLoading() {
|
|
148
165
|
return props.isLoading;
|
|
149
166
|
},
|
|
@@ -991,6 +1008,19 @@ function convexInfiniteQueryOptions(funcRef, args, opts = {}, meta) {
|
|
|
991
1008
|
/** Symbol key for attaching FunctionReference to options (non-serializable) */
|
|
992
1009
|
const FUNC_REF_SYMBOL = Symbol.for("convex.funcRef");
|
|
993
1010
|
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/internal/enabled.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Combine a computed gate with the caller's `enabled` option.
|
|
1015
|
+
* A predicate is preserved (never collapsed to a boolean) so TanStack Query
|
|
1016
|
+
* keeps evaluating it; `allowed: false` always wins.
|
|
1017
|
+
*/
|
|
1018
|
+
function resolveEnabled(allowed, enabled) {
|
|
1019
|
+
if (!allowed) return false;
|
|
1020
|
+
if (typeof enabled === "function") return (query) => enabled(query) !== false;
|
|
1021
|
+
return enabled ?? true;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
994
1024
|
//#endregion
|
|
995
1025
|
//#region src/solid/convex-solid.tsx
|
|
996
1026
|
/** @jsxImportSource solid-js */
|
|
@@ -1018,25 +1048,52 @@ function ConvexProvider(props) {
|
|
|
1018
1048
|
}
|
|
1019
1049
|
function ConvexProviderWithAuth(props) {
|
|
1020
1050
|
const client = props.client;
|
|
1051
|
+
const [authEpoch, setAuthEpoch] = createSignal(0);
|
|
1052
|
+
const [convexIdentity, setConvexIdentity] = createSignal(null);
|
|
1021
1053
|
const [isConvexLoading, setIsConvexLoading] = createSignal(true);
|
|
1022
1054
|
const [isConvexAuthenticated, setIsConvexAuthenticated] = createSignal(false);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
if (!authenticated) {
|
|
1029
|
-
client.clearAuth();
|
|
1055
|
+
const settleAuth = (nextIdentity, isAuthenticated) => {
|
|
1056
|
+
batch(() => {
|
|
1057
|
+
setConvexIdentity(nextIdentity);
|
|
1058
|
+
setAuthEpoch((epoch) => epoch + 1);
|
|
1059
|
+
setIsConvexAuthenticated(isAuthenticated);
|
|
1030
1060
|
setIsConvexLoading(false);
|
|
1031
|
-
|
|
1061
|
+
});
|
|
1062
|
+
};
|
|
1063
|
+
const authSnapshot = createMemo(() => props.useAuth(), void 0, { equals: false });
|
|
1064
|
+
const isAuthLoading = createMemo(() => authSnapshot().isLoading);
|
|
1065
|
+
const isAuthenticated = createMemo(() => authSnapshot().isAuthenticated);
|
|
1066
|
+
const fetchAccessToken = createMemo(() => authSnapshot().fetchAccessToken);
|
|
1067
|
+
const identity = createMemo(() => {
|
|
1068
|
+
const snapshot = authSnapshot();
|
|
1069
|
+
return snapshot.identity === void 0 ? Symbol("legacy-auth-transition") : snapshot.identity;
|
|
1070
|
+
});
|
|
1071
|
+
let authBindingGeneration = 0;
|
|
1072
|
+
createEffect(on([
|
|
1073
|
+
isAuthLoading,
|
|
1074
|
+
isAuthenticated,
|
|
1075
|
+
fetchAccessToken,
|
|
1076
|
+
identity
|
|
1077
|
+
], () => {
|
|
1078
|
+
const generation = ++authBindingGeneration;
|
|
1079
|
+
if (isAuthLoading()) {
|
|
1080
|
+
setIsConvexLoading(true);
|
|
1032
1081
|
return;
|
|
1033
1082
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1083
|
+
const nextIdentity = identity();
|
|
1084
|
+
setIsConvexLoading(true);
|
|
1085
|
+
if (!isAuthenticated()) {
|
|
1086
|
+
client.clearAuth();
|
|
1087
|
+
settleAuth(nextIdentity, false);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
client.setAuth(fetchAccessToken(), (isAuth) => {
|
|
1091
|
+
if (generation !== authBindingGeneration) return;
|
|
1092
|
+
settleAuth(nextIdentity, isAuth);
|
|
1037
1093
|
});
|
|
1038
|
-
});
|
|
1094
|
+
}));
|
|
1039
1095
|
onCleanup(() => {
|
|
1096
|
+
authBindingGeneration += 1;
|
|
1040
1097
|
client.clearAuth();
|
|
1041
1098
|
});
|
|
1042
1099
|
return createComponent(ConvexContext.Provider, {
|
|
@@ -1045,6 +1102,12 @@ function ConvexProviderWithAuth(props) {
|
|
|
1045
1102
|
},
|
|
1046
1103
|
get children() {
|
|
1047
1104
|
return createComponent(ConvexAuthBridge, {
|
|
1105
|
+
get authEpoch() {
|
|
1106
|
+
return authEpoch();
|
|
1107
|
+
},
|
|
1108
|
+
get identity() {
|
|
1109
|
+
return convexIdentity();
|
|
1110
|
+
},
|
|
1048
1111
|
get isAuthenticated() {
|
|
1049
1112
|
return isConvexAuthenticated();
|
|
1050
1113
|
},
|
|
@@ -1110,14 +1173,15 @@ function useConvexQueryOptions(funcRef, args, options) {
|
|
|
1110
1173
|
skipUnauth: options?.skipUnauth
|
|
1111
1174
|
});
|
|
1112
1175
|
const baseOptions = convexQuery(funcRef, isSkipped ? {} : args);
|
|
1113
|
-
const {
|
|
1176
|
+
const { enabled: userEnabled, skipUnauth, subscribe, ...queryOptions } = options ?? {};
|
|
1114
1177
|
return {
|
|
1115
1178
|
...baseOptions,
|
|
1116
1179
|
...queryOptions,
|
|
1117
|
-
enabled: isSkipped
|
|
1180
|
+
enabled: resolveEnabled(!(isSkipped || authSkip.shouldSkip), userEnabled),
|
|
1118
1181
|
meta: {
|
|
1119
1182
|
...baseOptions.meta,
|
|
1120
1183
|
authType: authSkip.authType,
|
|
1184
|
+
skipUnauth,
|
|
1121
1185
|
subscribe: subscribe !== false
|
|
1122
1186
|
}
|
|
1123
1187
|
};
|
|
@@ -1155,13 +1219,15 @@ function useConvexInfiniteQueryOptions(funcRef, args, opts) {
|
|
|
1155
1219
|
enabled: isSkipped ? false : enabledOpt,
|
|
1156
1220
|
skipUnauth: opts.skipUnauth
|
|
1157
1221
|
});
|
|
1158
|
-
const enabled = isSkipped || authSkip.shouldSkip ? false : enabledOpt;
|
|
1159
1222
|
const baseOptions = convexInfiniteQueryOptions(funcRef, isSkipped ? {} : args, {
|
|
1160
1223
|
...opts,
|
|
1161
|
-
enabled
|
|
1224
|
+
enabled: void 0
|
|
1162
1225
|
}, meta);
|
|
1163
1226
|
return {
|
|
1164
1227
|
...baseOptions,
|
|
1228
|
+
get enabled() {
|
|
1229
|
+
return resolveEnabled(!(isSkipped || authSkip.shouldSkip), opts.enabled);
|
|
1230
|
+
},
|
|
1165
1231
|
meta: {
|
|
1166
1232
|
...baseOptions.meta,
|
|
1167
1233
|
authType: authSkip.authType
|
|
@@ -1195,11 +1261,16 @@ function useConvexActionQueryOptions(action, args, options) {
|
|
|
1195
1261
|
skipUnauth: options?.skipUnauth
|
|
1196
1262
|
});
|
|
1197
1263
|
const baseOptions = convexAction(action, isSkipped ? {} : args);
|
|
1198
|
-
const {
|
|
1264
|
+
const { enabled: userEnabled, skipUnauth, ...queryOptions } = options ?? {};
|
|
1199
1265
|
return {
|
|
1200
1266
|
...baseOptions,
|
|
1201
1267
|
...queryOptions,
|
|
1202
|
-
enabled: isSkipped
|
|
1268
|
+
enabled: resolveEnabled(!(isSkipped || authSkip.shouldSkip), userEnabled),
|
|
1269
|
+
meta: {
|
|
1270
|
+
...baseOptions.meta,
|
|
1271
|
+
authType: authSkip.authType,
|
|
1272
|
+
skipUnauth
|
|
1273
|
+
}
|
|
1203
1274
|
};
|
|
1204
1275
|
}
|
|
1205
1276
|
/**
|
|
@@ -1528,7 +1599,39 @@ function createCRPCContext(options) {
|
|
|
1528
1599
|
const HttpProxyContext = createContext(void 0);
|
|
1529
1600
|
function CRPCProvider(props) {
|
|
1530
1601
|
const authStore = useAuthStore();
|
|
1602
|
+
const bridgeAuth = useConvexAuthBridge();
|
|
1603
|
+
const safeAuth = useSafeConvexAuth();
|
|
1604
|
+
const auth = bridgeAuth ?? safeAuth;
|
|
1531
1605
|
const fetchAccessToken = useFetchAccessToken();
|
|
1606
|
+
props.convexQueryClient.updateAuthStore(authStore.store ? authStore : void 0);
|
|
1607
|
+
let previousAuth;
|
|
1608
|
+
let authGeneration = 0;
|
|
1609
|
+
let transitionClear;
|
|
1610
|
+
createEffect(() => {
|
|
1611
|
+
const generation = ++authGeneration;
|
|
1612
|
+
const currentAuth = {
|
|
1613
|
+
identity: auth.identity,
|
|
1614
|
+
isAuthenticated: auth.isAuthenticated,
|
|
1615
|
+
isLoading: auth.isLoading
|
|
1616
|
+
};
|
|
1617
|
+
const previous = previousAuth;
|
|
1618
|
+
previousAuth = currentAuth;
|
|
1619
|
+
if (currentAuth.isLoading) {
|
|
1620
|
+
if (!previous?.isLoading) transitionClear = props.convexQueryClient.resetAuthQueries({ refetch: false });
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (previous?.isLoading) {
|
|
1624
|
+
const pendingClear = transitionClear;
|
|
1625
|
+
transitionClear = void 0;
|
|
1626
|
+
(async () => {
|
|
1627
|
+
const clientGeneration = await pendingClear;
|
|
1628
|
+
if (generation !== authGeneration) return;
|
|
1629
|
+
await props.convexQueryClient.resetAuthQueries({ generation: clientGeneration });
|
|
1630
|
+
})();
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (!previous || previous.identity !== currentAuth.identity || previous.isAuthenticated !== currentAuth.isAuthenticated) props.convexQueryClient.resetAuthQueries();
|
|
1634
|
+
});
|
|
1532
1635
|
const proxy = createCRPCOptionsProxy(api, meta, options.transformer);
|
|
1533
1636
|
const vanillaClient = createVanillaCRPCProxy(api, meta, props.convexClient, options.transformer);
|
|
1534
1637
|
const httpProxy = (() => {
|
|
@@ -1770,6 +1873,56 @@ function createAuthMutations(authClient) {
|
|
|
1770
1873
|
};
|
|
1771
1874
|
}
|
|
1772
1875
|
|
|
1876
|
+
//#endregion
|
|
1877
|
+
//#region src/internal/auth-reset.ts
|
|
1878
|
+
/**
|
|
1879
|
+
* Erase the cached result of every auth-bound query.
|
|
1880
|
+
*
|
|
1881
|
+
* `queryClient.resetQueries()` is not enough. It restores each query's
|
|
1882
|
+
* `initialState`, and query-core derives that from `initialData` when the query
|
|
1883
|
+
* is built, never re-deriving it once real data lands. An auth-bound entry
|
|
1884
|
+
* therefore comes back holding the previous account's rows marked `success`,
|
|
1885
|
+
* and nothing corrects it: Convex query options set `staleTime: Infinity` with
|
|
1886
|
+
* every refetch trigger off, and `resetQueries` only refetches entries that are
|
|
1887
|
+
* currently active.
|
|
1888
|
+
*
|
|
1889
|
+
* Every entry is removed so query-core forgets both its current state and its
|
|
1890
|
+
* private `initialState`. Mounted observers are rebound with `initialData`
|
|
1891
|
+
* removed, creating a pristine replacement query that future public resets
|
|
1892
|
+
* cannot use to resurrect the previous account's rows.
|
|
1893
|
+
*/
|
|
1894
|
+
async function clearAuthBoundQueries(cache, isAuthBound) {
|
|
1895
|
+
const authQueries = cache.getAll().filter((query) => isAuthBound(query));
|
|
1896
|
+
const restoreObservers = [];
|
|
1897
|
+
await Promise.all(authQueries.map((query) => query.cancel({ silent: true })));
|
|
1898
|
+
for (const query of authQueries) {
|
|
1899
|
+
const observers = [...query.observers];
|
|
1900
|
+
cache.remove(query);
|
|
1901
|
+
for (const observer of observers) {
|
|
1902
|
+
const previousOptions = observer.options;
|
|
1903
|
+
observer.setOptions({
|
|
1904
|
+
...observer.options,
|
|
1905
|
+
enabled: false,
|
|
1906
|
+
initialData: void 0,
|
|
1907
|
+
placeholderData: void 0
|
|
1908
|
+
});
|
|
1909
|
+
const suspendedOptions = observer.options;
|
|
1910
|
+
restoreObservers.push(() => {
|
|
1911
|
+
const currentOptions = observer.options;
|
|
1912
|
+
observer.setOptions({
|
|
1913
|
+
...currentOptions,
|
|
1914
|
+
enabled: observer.options === suspendedOptions ? previousOptions.enabled : currentOptions.enabled,
|
|
1915
|
+
initialData: void 0,
|
|
1916
|
+
placeholderData: void 0
|
|
1917
|
+
});
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return () => {
|
|
1922
|
+
for (const restoreObserver of restoreObservers) restoreObserver();
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1773
1926
|
//#endregion
|
|
1774
1927
|
//#region src/internal/query-key.ts
|
|
1775
1928
|
/**
|
|
@@ -1828,6 +1981,31 @@ function createHashFn(fallback = hashKey) {
|
|
|
1828
1981
|
};
|
|
1829
1982
|
}
|
|
1830
1983
|
|
|
1984
|
+
//#endregion
|
|
1985
|
+
//#region src/internal/subscription-gate.ts
|
|
1986
|
+
/** Read kitcn's meta off a TanStack query. */
|
|
1987
|
+
function readConvexQueryMeta(query) {
|
|
1988
|
+
return query.meta;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Auth-bound queries are cleared and resubscribed on an identity transition so
|
|
1992
|
+
* one account never renders another account's cached rows.
|
|
1993
|
+
*/
|
|
1994
|
+
function isAuthBoundQuery(query) {
|
|
1995
|
+
const authType = readConvexQueryMeta(query)?.authType;
|
|
1996
|
+
return authType === "required" || authType === "optional";
|
|
1997
|
+
}
|
|
1998
|
+
/** Whether a Convex subscription may be opened for this query. */
|
|
1999
|
+
function canSubscribeQuery(query, opts) {
|
|
2000
|
+
if (opts.isSubscribed) return false;
|
|
2001
|
+
const meta = readConvexQueryMeta(query);
|
|
2002
|
+
if (meta?.subscribe === false) return false;
|
|
2003
|
+
if (query.getObserversCount() === 0) return false;
|
|
2004
|
+
if (query.isDisabled()) return false;
|
|
2005
|
+
if (opts.shouldSkipSubscription(meta?.authType)) return false;
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1831
2009
|
//#endregion
|
|
1832
2010
|
//#region src/solid/client.ts
|
|
1833
2011
|
/**
|
|
@@ -1920,6 +2098,12 @@ var ConvexQueryClient = class {
|
|
|
1920
2098
|
ssrQueryMode;
|
|
1921
2099
|
/** Auth store for checking auth state */
|
|
1922
2100
|
authStore;
|
|
2101
|
+
/** In-flight clear shared by overlapping account identity transitions. */
|
|
2102
|
+
authBarrierClear;
|
|
2103
|
+
/** Blocks auth-bound work created while Convex is changing identity. */
|
|
2104
|
+
authSettlementBarrier;
|
|
2105
|
+
/** Latest account transition allowed to restore and refetch observers. */
|
|
2106
|
+
authResetGeneration = 0;
|
|
1923
2107
|
/** Delay before unsubscribing when query has no observers */
|
|
1924
2108
|
unsubscribeDelay;
|
|
1925
2109
|
/** Payload transformer used across request/response boundaries. */
|
|
@@ -1947,6 +2131,18 @@ var ConvexQueryClient = class {
|
|
|
1947
2131
|
sub.unsubscribe();
|
|
1948
2132
|
delete this.subscriptions[queryHash];
|
|
1949
2133
|
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Open a Convex subscription for a query, if the shared gate allows it.
|
|
2136
|
+
* Single owner of the subscribe preconditions for every cache-event branch.
|
|
2137
|
+
*/
|
|
2138
|
+
subscribeQuery(query) {
|
|
2139
|
+
if (!canSubscribeQuery(query, {
|
|
2140
|
+
isSubscribed: !!this.subscriptions[query.queryHash],
|
|
2141
|
+
shouldSkipSubscription: (authType) => this.shouldSkipSubscription(authType)
|
|
2142
|
+
})) return;
|
|
2143
|
+
const [, funcName, args] = query.queryKey;
|
|
2144
|
+
this.createSubscription(query.queryHash, funcName, args, query.queryKey);
|
|
2145
|
+
}
|
|
1950
2146
|
/** Update auth store (for HMR where store may reset) */
|
|
1951
2147
|
updateAuthStore(authStore) {
|
|
1952
2148
|
this.authStore = authStore;
|
|
@@ -1966,12 +2162,30 @@ var ConvexQueryClient = class {
|
|
|
1966
2162
|
* Needed for useSuspenseQuery which ignores enabled: false.
|
|
1967
2163
|
*/
|
|
1968
2164
|
shouldSkipSubscription(authType) {
|
|
1969
|
-
if (!authType
|
|
2165
|
+
if (!authType) return false;
|
|
2166
|
+
if (this.authSettlementBarrier) return true;
|
|
2167
|
+
if (!this.authStore) return false;
|
|
1970
2168
|
const authState = this.getAuthState();
|
|
1971
2169
|
if (authState?.isLoading) return true;
|
|
1972
2170
|
if (authType === "required" && !authState?.isAuthenticated) return true;
|
|
1973
2171
|
return false;
|
|
1974
2172
|
}
|
|
2173
|
+
/** Hold new auth-bound requests until the latest Convex identity settles. */
|
|
2174
|
+
beginAuthSettlementBarrier() {
|
|
2175
|
+
if (this.authSettlementBarrier) return;
|
|
2176
|
+
let resolve = () => {};
|
|
2177
|
+
this.authSettlementBarrier = {
|
|
2178
|
+
promise: new Promise((resolvePromise) => {
|
|
2179
|
+
resolve = resolvePromise;
|
|
2180
|
+
}),
|
|
2181
|
+
resolve
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
/** Wait through overlapping barriers and stop canceled requests. */
|
|
2185
|
+
async waitForAuthSettlement(signal) {
|
|
2186
|
+
while (this.authSettlementBarrier) await this.authSettlementBarrier.promise;
|
|
2187
|
+
signal?.throwIfAborted();
|
|
2188
|
+
}
|
|
1975
2189
|
/** Get QueryClient, throwing if not connected */
|
|
1976
2190
|
get queryClient() {
|
|
1977
2191
|
if (!this._queryClient) throw new Error("ConvexQueryClient not connected to TanStack QueryClient.");
|
|
@@ -2034,6 +2248,62 @@ var ConvexQueryClient = class {
|
|
|
2034
2248
|
}
|
|
2035
2249
|
}
|
|
2036
2250
|
/**
|
|
2251
|
+
* Advance the account generation published by the auth store.
|
|
2252
|
+
* Client state that only makes sense inside one account's result set —
|
|
2253
|
+
* paginated cursor chains — keys on it, so it is rebuilt instead of reused.
|
|
2254
|
+
*/
|
|
2255
|
+
bumpAuthEpoch() {
|
|
2256
|
+
if (!this.authStore) return;
|
|
2257
|
+
this.authStore.set("authEpoch", this.authStore.get("authEpoch") + 1);
|
|
2258
|
+
}
|
|
2259
|
+
/** Drop auth-bound data without fetching while Convex changes identity. */
|
|
2260
|
+
async clearAuthQueries() {
|
|
2261
|
+
const queryCache = this.queryClient.getQueryCache();
|
|
2262
|
+
for (const query of queryCache.getAll()) {
|
|
2263
|
+
if (!isAuthBoundQuery(query)) continue;
|
|
2264
|
+
this.cancelPendingUnsubscribe(query.queryHash);
|
|
2265
|
+
this.unsubscribeQueryByHash(query.queryHash);
|
|
2266
|
+
}
|
|
2267
|
+
this.bumpAuthEpoch();
|
|
2268
|
+
return clearAuthBoundQueries(queryCache, (query) => isAuthBoundQuery(query));
|
|
2269
|
+
}
|
|
2270
|
+
/** Refetch and resubscribe auth-bound queries after Convex settles. */
|
|
2271
|
+
async refetchAuthQueries(generation) {
|
|
2272
|
+
const queryCache = this.queryClient.getQueryCache();
|
|
2273
|
+
await this.queryClient.refetchQueries({
|
|
2274
|
+
predicate: (query) => isAuthBoundQuery(query),
|
|
2275
|
+
type: "active"
|
|
2276
|
+
});
|
|
2277
|
+
if (generation !== this.authResetGeneration) return;
|
|
2278
|
+
for (const query of queryCache.getAll()) if (isAuthBoundQuery(query)) this.subscribeQuery(query);
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Drop every auth-bound cache entry and resubscribe.
|
|
2282
|
+
* Call after an identity transition when no separate settlement barrier owns
|
|
2283
|
+
* the clear/refetch phases.
|
|
2284
|
+
*/
|
|
2285
|
+
async resetAuthQueries(options = {}) {
|
|
2286
|
+
if (options.refetch === false) {
|
|
2287
|
+
const generation = ++this.authResetGeneration;
|
|
2288
|
+
this.beginAuthSettlementBarrier();
|
|
2289
|
+
this.authBarrierClear ??= this.clearAuthQueries();
|
|
2290
|
+
await this.authBarrierClear;
|
|
2291
|
+
return generation;
|
|
2292
|
+
}
|
|
2293
|
+
const generation = options.generation ?? ++this.authResetGeneration;
|
|
2294
|
+
if (generation !== this.authResetGeneration) return generation;
|
|
2295
|
+
this.authBarrierClear ??= this.clearAuthQueries();
|
|
2296
|
+
const restoreObservers = await this.authBarrierClear;
|
|
2297
|
+
if (generation !== this.authResetGeneration) return generation;
|
|
2298
|
+
this.authBarrierClear = void 0;
|
|
2299
|
+
const settlementBarrier = this.authSettlementBarrier;
|
|
2300
|
+
this.authSettlementBarrier = void 0;
|
|
2301
|
+
settlementBarrier?.resolve();
|
|
2302
|
+
restoreObservers();
|
|
2303
|
+
await this.refetchAuthQueries(generation);
|
|
2304
|
+
return generation;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2037
2307
|
* Batch update all subscriptions.
|
|
2038
2308
|
* Called internally when Convex client reconnects.
|
|
2039
2309
|
*/
|
|
@@ -2107,26 +2377,13 @@ var ConvexQueryClient = class {
|
|
|
2107
2377
|
this.cancelPendingUnsubscribe(event.query.queryHash);
|
|
2108
2378
|
this.unsubscribeQueryByHash(event.query.queryHash);
|
|
2109
2379
|
break;
|
|
2110
|
-
case "added":
|
|
2111
|
-
|
|
2112
|
-
if (meta?.subscribe === false) break;
|
|
2113
|
-
const [, funcName, args] = event.query.queryKey;
|
|
2114
|
-
if (event.query.getObserversCount() === 0) break;
|
|
2115
|
-
if (this.shouldSkipSubscription(meta?.authType)) break;
|
|
2116
|
-
this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
|
|
2380
|
+
case "added":
|
|
2381
|
+
this.subscribeQuery(event.query);
|
|
2117
2382
|
break;
|
|
2118
|
-
|
|
2119
|
-
case "observerAdded": {
|
|
2383
|
+
case "observerAdded":
|
|
2120
2384
|
this.cancelPendingUnsubscribe(event.query.queryHash);
|
|
2121
|
-
|
|
2122
|
-
if (event.query.options.enabled === false) break;
|
|
2123
|
-
const meta = event.query.meta;
|
|
2124
|
-
if (meta?.subscribe === false) break;
|
|
2125
|
-
const [, funcName, args] = event.query.queryKey;
|
|
2126
|
-
if (this.shouldSkipSubscription(meta?.authType)) break;
|
|
2127
|
-
this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
|
|
2385
|
+
this.subscribeQuery(event.query);
|
|
2128
2386
|
break;
|
|
2129
|
-
}
|
|
2130
2387
|
case "observerRemoved":
|
|
2131
2388
|
if (event.query.getObserversCount() === 0 && this.subscriptions[event.query.queryHash]) {
|
|
2132
2389
|
const queryHash = event.query.queryHash;
|
|
@@ -2142,7 +2399,7 @@ var ConvexQueryClient = class {
|
|
|
2142
2399
|
if (event.action.type === "setState" && event.action.setStateOptions?.meta === "set by ConvexQueryClient") break;
|
|
2143
2400
|
break;
|
|
2144
2401
|
case "observerOptionsUpdated": {
|
|
2145
|
-
const isDisabled = event.query.
|
|
2402
|
+
const isDisabled = event.query.isDisabled();
|
|
2146
2403
|
const isSubscribed = !!this.subscriptions[event.query.queryHash];
|
|
2147
2404
|
if (isDisabled && isSubscribed) {
|
|
2148
2405
|
this.cancelPendingUnsubscribe(event.query.queryHash);
|
|
@@ -2150,11 +2407,7 @@ var ConvexQueryClient = class {
|
|
|
2150
2407
|
break;
|
|
2151
2408
|
}
|
|
2152
2409
|
if (isSubscribed || isDisabled) break;
|
|
2153
|
-
|
|
2154
|
-
if (meta?.subscribe === false) break;
|
|
2155
|
-
const [, funcName, args] = event.query.queryKey;
|
|
2156
|
-
if (this.shouldSkipSubscription(meta?.authType)) break;
|
|
2157
|
-
this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
|
|
2410
|
+
this.subscribeQuery(event.query);
|
|
2158
2411
|
break;
|
|
2159
2412
|
}
|
|
2160
2413
|
}
|
|
@@ -2197,6 +2450,7 @@ var ConvexQueryClient = class {
|
|
|
2197
2450
|
return async (context) => {
|
|
2198
2451
|
const { queryKey, meta: rawMeta } = context;
|
|
2199
2452
|
const meta = rawMeta;
|
|
2453
|
+
if (!isServer && meta?.authType) await this.waitForAuthSettlement(context.signal);
|
|
2200
2454
|
if (isConvexSkipped(queryKey)) throw new Error("Skipped query should not actually run, should { enabled: false }");
|
|
2201
2455
|
if (isConvexQuery(queryKey)) {
|
|
2202
2456
|
const [, funcName, args] = queryKey;
|
|
@@ -2268,6 +2522,38 @@ function throwBecauseNotConvexQuery(context) {
|
|
|
2268
2522
|
throw new Error(`Query key is not for a Convex Query: ${context.queryKey}`);
|
|
2269
2523
|
}
|
|
2270
2524
|
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/internal/jwt.ts
|
|
2527
|
+
const VOLATILE_JWT_CLAIMS = new Set([
|
|
2528
|
+
"exp",
|
|
2529
|
+
"iat",
|
|
2530
|
+
"jti",
|
|
2531
|
+
"nbf"
|
|
2532
|
+
]);
|
|
2533
|
+
const decodeJwtPayload = (token) => {
|
|
2534
|
+
try {
|
|
2535
|
+
const segment = token.split(".")[1];
|
|
2536
|
+
if (!segment) return null;
|
|
2537
|
+
const binary = atob(segment.replaceAll("-", "+").replaceAll("_", "/"));
|
|
2538
|
+
const payload = JSON.parse(new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0))));
|
|
2539
|
+
return typeof payload === "object" && payload !== null ? payload : null;
|
|
2540
|
+
} catch {
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
const stableStringify = (value) => {
|
|
2545
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
2546
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
2547
|
+
return `{${Object.entries(value).filter(([, claim]) => claim !== void 0).sort(([a], [b]) => a < b ? -1 : 1).map(([key, claim]) => `${JSON.stringify(key)}:${stableStringify(claim)}`).join(",")}}`;
|
|
2548
|
+
};
|
|
2549
|
+
const decodeJwtIdentity = (token) => {
|
|
2550
|
+
const payload = decodeJwtPayload(token);
|
|
2551
|
+
if (!payload) return null;
|
|
2552
|
+
const claims = {};
|
|
2553
|
+
for (const [key, claim] of Object.entries(payload)) if (!VOLATILE_JWT_CLAIMS.has(key)) claims[key] = claim;
|
|
2554
|
+
return stableStringify(claims);
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2271
2557
|
//#endregion
|
|
2272
2558
|
//#region src/solid/convex-auth-provider.tsx
|
|
2273
2559
|
/** @jsxImportSource solid-js */
|
|
@@ -2287,6 +2573,13 @@ const hasActiveSessionData = (session) => {
|
|
|
2287
2573
|
if (!session || typeof session !== "object") return false;
|
|
2288
2574
|
return Boolean(session.session);
|
|
2289
2575
|
};
|
|
2576
|
+
const getSessionId = (sessionData) => {
|
|
2577
|
+
if (!sessionData || typeof sessionData !== "object") return;
|
|
2578
|
+
const session = sessionData.session;
|
|
2579
|
+
if (!session || typeof session !== "object") return;
|
|
2580
|
+
const id = session.id;
|
|
2581
|
+
return typeof id === "string" ? id : void 0;
|
|
2582
|
+
};
|
|
2290
2583
|
/**
|
|
2291
2584
|
* Unified auth provider for Convex + Better Auth (SolidJS).
|
|
2292
2585
|
* Handles token sync and auth callbacks.
|
|
@@ -2336,21 +2629,40 @@ function ConvexAuthProviderInner(props) {
|
|
|
2336
2629
|
const authStore = useAuthStore();
|
|
2337
2630
|
const sessionAccessor = props.authClient.useSession();
|
|
2338
2631
|
let pendingTokenPromise = null;
|
|
2632
|
+
let pendingTokenSessionId;
|
|
2633
|
+
let authIdentity = null;
|
|
2634
|
+
let identityClaims = null;
|
|
2635
|
+
let identityInitialized = false;
|
|
2636
|
+
let identitySessionId;
|
|
2637
|
+
let tokenSessionId;
|
|
2638
|
+
let tokenSessionInitialized = false;
|
|
2339
2639
|
createEffect(() => {
|
|
2340
2640
|
const sessionState = sessionAccessor();
|
|
2341
2641
|
const session = sessionState.data;
|
|
2342
2642
|
const isPending = sessionState.isPending;
|
|
2643
|
+
const sessionId = getSessionId(session);
|
|
2343
2644
|
if (!hasActiveSessionData(session) && !isPending) {
|
|
2344
2645
|
authStore.set("token", null);
|
|
2345
2646
|
authStore.set("expiresAt", null);
|
|
2346
2647
|
authStore.set("isAuthenticated", false);
|
|
2648
|
+
tokenSessionId = void 0;
|
|
2649
|
+
tokenSessionInitialized = false;
|
|
2650
|
+
} else if (hasActiveSessionData(session)) {
|
|
2651
|
+
if (!tokenSessionInitialized && authStore.get("token") !== null || tokenSessionInitialized && sessionId !== tokenSessionId) {
|
|
2652
|
+
authStore.set("token", null);
|
|
2653
|
+
authStore.set("expiresAt", null);
|
|
2654
|
+
}
|
|
2655
|
+
tokenSessionId = sessionId;
|
|
2656
|
+
tokenSessionInitialized = true;
|
|
2347
2657
|
}
|
|
2348
2658
|
});
|
|
2349
2659
|
const fetchAccessToken = async ({ forceRefreshToken = false } = {}) => {
|
|
2350
2660
|
const sessionState = sessionAccessor();
|
|
2351
2661
|
const currentSession = sessionState.data;
|
|
2352
2662
|
const currentIsPending = sessionState.isPending;
|
|
2353
|
-
|
|
2663
|
+
const hasSession = hasActiveSessionData(currentSession);
|
|
2664
|
+
const currentSessionId = getSessionId(currentSession);
|
|
2665
|
+
if (!hasSession) {
|
|
2354
2666
|
if (!currentIsPending) {
|
|
2355
2667
|
authStore.set("token", null);
|
|
2356
2668
|
authStore.set("expiresAt", null);
|
|
@@ -2360,11 +2672,17 @@ function ConvexAuthProviderInner(props) {
|
|
|
2360
2672
|
const cachedToken = authStore.get("token");
|
|
2361
2673
|
const expiresAt = authStore.get("expiresAt");
|
|
2362
2674
|
const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
|
|
2363
|
-
if (!forceRefreshToken && cachedToken && expiresAt && timeRemaining >= 6e4) return cachedToken;
|
|
2364
|
-
if (!forceRefreshToken && pendingTokenPromise) return pendingTokenPromise;
|
|
2675
|
+
if (!forceRefreshToken && cachedToken && expiresAt && currentSessionId === tokenSessionId && timeRemaining >= 6e4) return cachedToken;
|
|
2676
|
+
if (!forceRefreshToken && pendingTokenPromise && pendingTokenSessionId === currentSessionId) return pendingTokenPromise;
|
|
2365
2677
|
const fetchOptions = { throw: false };
|
|
2366
|
-
if (cachedToken && decodeJwtExp(cachedToken) === null) fetchOptions.headers = { Authorization: `Bearer ${cachedToken}` };
|
|
2367
|
-
|
|
2678
|
+
if (cachedToken && currentSessionId === tokenSessionId && decodeJwtExp(cachedToken) === null) fetchOptions.headers = { Authorization: `Bearer ${cachedToken}` };
|
|
2679
|
+
const sessionStillOwnsRequest = () => {
|
|
2680
|
+
const latestSession = sessionAccessor().data;
|
|
2681
|
+
return hasActiveSessionData(latestSession) && getSessionId(latestSession) === currentSessionId;
|
|
2682
|
+
};
|
|
2683
|
+
let tokenPromise;
|
|
2684
|
+
tokenPromise = props.authClient.convex.token({ fetchOptions }).then((result) => {
|
|
2685
|
+
if (!sessionStillOwnsRequest()) return null;
|
|
2368
2686
|
const jwt = result.data?.token || null;
|
|
2369
2687
|
if (jwt) {
|
|
2370
2688
|
const exp = decodeJwtExp(jwt);
|
|
@@ -2376,21 +2694,47 @@ function ConvexAuthProviderInner(props) {
|
|
|
2376
2694
|
authStore.set("expiresAt", null);
|
|
2377
2695
|
return null;
|
|
2378
2696
|
}).catch((error) => {
|
|
2697
|
+
if (!sessionStillOwnsRequest()) return null;
|
|
2379
2698
|
authStore.set("token", null);
|
|
2380
2699
|
authStore.set("expiresAt", null);
|
|
2381
2700
|
console.error("[fetchAccessToken] error", error);
|
|
2382
2701
|
return null;
|
|
2383
2702
|
}).finally(() => {
|
|
2384
|
-
pendingTokenPromise
|
|
2703
|
+
if (pendingTokenPromise === tokenPromise) {
|
|
2704
|
+
pendingTokenPromise = null;
|
|
2705
|
+
pendingTokenSessionId = void 0;
|
|
2706
|
+
}
|
|
2385
2707
|
});
|
|
2386
|
-
|
|
2708
|
+
pendingTokenPromise = tokenPromise;
|
|
2709
|
+
pendingTokenSessionId = currentSessionId;
|
|
2710
|
+
return tokenPromise;
|
|
2387
2711
|
};
|
|
2388
2712
|
const useAuth = () => {
|
|
2389
2713
|
const sessionState = sessionAccessor();
|
|
2390
2714
|
const hasSession = hasActiveSessionData(sessionState.data);
|
|
2391
2715
|
const sessionMissing = !hasSession && !sessionState.isPending;
|
|
2392
2716
|
const token = authStore.get("token");
|
|
2717
|
+
const sessionId = getSessionId(sessionState.data);
|
|
2718
|
+
const claims = token && tokenSessionInitialized && sessionId === tokenSessionId ? decodeJwtIdentity(token) : null;
|
|
2719
|
+
const sessionChanged = identityInitialized && sessionId !== identitySessionId;
|
|
2720
|
+
if (!identityInitialized || sessionChanged) {
|
|
2721
|
+
identityInitialized = true;
|
|
2722
|
+
identitySessionId = sessionId;
|
|
2723
|
+
identityClaims = sessionChanged ? null : claims;
|
|
2724
|
+
authIdentity = !sessionChanged && claims ? JSON.stringify({
|
|
2725
|
+
claims,
|
|
2726
|
+
sessionId: sessionId ?? null
|
|
2727
|
+
}) : sessionId ?? null;
|
|
2728
|
+
} else if (claims && identityClaims === null) identityClaims = claims;
|
|
2729
|
+
else if (claims && claims !== identityClaims) {
|
|
2730
|
+
identityClaims = claims;
|
|
2731
|
+
authIdentity = JSON.stringify({
|
|
2732
|
+
claims,
|
|
2733
|
+
sessionId: sessionId ?? null
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2393
2736
|
return {
|
|
2737
|
+
identity: authIdentity,
|
|
2394
2738
|
isLoading: sessionState.isPending && !token,
|
|
2395
2739
|
isAuthenticated: sessionMissing ? false : hasSession || token !== null,
|
|
2396
2740
|
fetchAccessToken
|
|
@@ -2543,15 +2887,8 @@ function createQueriesResults(queries) {
|
|
|
2543
2887
|
//#endregion
|
|
2544
2888
|
//#region src/solid/use-infinite-query.ts
|
|
2545
2889
|
const PAGINATION_KEY_PREFIX = "__pagination__";
|
|
2546
|
-
const paginationIdStore = /* @__PURE__ */ new Map();
|
|
2547
2890
|
let paginationIdCounter = 0;
|
|
2548
|
-
const
|
|
2549
|
-
const existing = paginationIdStore.get(storeKey);
|
|
2550
|
-
if (existing !== void 0) return existing;
|
|
2551
|
-
const newId = ++paginationIdCounter;
|
|
2552
|
-
paginationIdStore.set(storeKey, newId);
|
|
2553
|
-
return newId;
|
|
2554
|
-
};
|
|
2891
|
+
const createPaginationId = () => ++paginationIdCounter;
|
|
2555
2892
|
/** Read the identity of a Convex document, tolerating `id` and `_id` shapes */
|
|
2556
2893
|
const getItemId = (item) => {
|
|
2557
2894
|
const doc = item;
|
|
@@ -2682,8 +3019,9 @@ const useStaleCursorRecovery = ({ argsObject, combined, limit, pageResults, setS
|
|
|
2682
3019
|
* Use `useInfiniteQuery` for the public API with auth handling.
|
|
2683
3020
|
*/
|
|
2684
3021
|
const useInfiniteQueryInternal = (query, args, options) => {
|
|
2685
|
-
const { limit, enabled, placeholderData, ...queryOptions } = options;
|
|
3022
|
+
const { limit, authType, enabled, placeholderData, ...queryOptions } = options;
|
|
2686
3023
|
const safeAuth = useSafeConvexAuth();
|
|
3024
|
+
const authEpoch = () => useAuthValue("authEpoch");
|
|
2687
3025
|
const meta = useMeta();
|
|
2688
3026
|
const queryClient = useQueryClient();
|
|
2689
3027
|
const prefetchedFirstPage = createMemo(() => {
|
|
@@ -2698,7 +3036,7 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2698
3036
|
];
|
|
2699
3037
|
return queryClient.getQueryData(serverQueryKey) ?? null;
|
|
2700
3038
|
});
|
|
2701
|
-
const skip = createMemo(() => !prefetchedFirstPage() && (safeAuth.isLoading || enabled === false));
|
|
3039
|
+
const skip = createMemo(() => !prefetchedFirstPage() && (safeAuth.isLoading || enabled?.() === false));
|
|
2702
3040
|
const getPaginationState = (key) => {
|
|
2703
3041
|
const queryKey = [PAGINATION_KEY_PREFIX, key];
|
|
2704
3042
|
return queryClient.getQueryData(queryKey);
|
|
@@ -2710,10 +3048,11 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2710
3048
|
const argsObject = createMemo(() => skip() ? {} : args);
|
|
2711
3049
|
const storeKey = createMemo(() => JSON.stringify({
|
|
2712
3050
|
query: getFunctionName(query),
|
|
2713
|
-
args: argsObject()
|
|
3051
|
+
args: argsObject(),
|
|
3052
|
+
...authType ? { authEpoch: authEpoch() } : {}
|
|
2714
3053
|
}));
|
|
2715
3054
|
const createInitialState = () => {
|
|
2716
|
-
const id =
|
|
3055
|
+
const id = createPaginationId();
|
|
2717
3056
|
return {
|
|
2718
3057
|
id,
|
|
2719
3058
|
nextPageKey: 1,
|
|
@@ -2790,10 +3129,9 @@ const useInfiniteQueryInternal = (query, args, options) => {
|
|
|
2790
3129
|
const pageArgs = state().queries[key]?.args;
|
|
2791
3130
|
return {
|
|
2792
3131
|
...convexQuery(query, pageArgs ? (({ __paginationId, ...rest }) => rest)(pageArgs) : "skip", meta),
|
|
2793
|
-
enabled: !skip() && !!state().queries[key],
|
|
3132
|
+
enabled: resolveEnabled(!skip() && !!state().queries[key] && (!authType || !safeAuth.isLoading), enabled?.()),
|
|
2794
3133
|
structuralSharing: false,
|
|
2795
3134
|
...queryOptions ?? {},
|
|
2796
|
-
...index === 0 && prefetchedFirstPage() ? { initialData: prefetchedFirstPage() } : {},
|
|
2797
3135
|
...index === 0 && placeholderData ? { placeholderData: {
|
|
2798
3136
|
page: placeholderData,
|
|
2799
3137
|
isDone: false,
|
|
@@ -2941,34 +3279,36 @@ function useInfiniteQuery(infiniteOptions) {
|
|
|
2941
3279
|
const query = infiniteOptions[FUNC_REF_SYMBOL];
|
|
2942
3280
|
const onQueryUnauthorized = useAuthValue("onQueryUnauthorized");
|
|
2943
3281
|
const safeAuth = useSafeConvexAuth();
|
|
2944
|
-
const { queryKey: _queryKey, staleTime: _staleTime, refetchInterval: _refetchInterval, refetchOnMount: _refetchOnMount, refetchOnReconnect: _refetchOnReconnect, refetchOnWindowFocus: _refetchOnWindowFocus, enabled:
|
|
3282
|
+
const { queryKey: _queryKey, staleTime: _staleTime, refetchInterval: _refetchInterval, refetchOnMount: _refetchOnMount, refetchOnReconnect: _refetchOnReconnect, refetchOnWindowFocus: _refetchOnWindowFocus, enabled: _enabled, meta, ...queryOptions } = infiniteOptions;
|
|
2945
3283
|
const { queryName, args, limit, authType, skipUnauth } = meta;
|
|
2946
3284
|
const skipUnauthFinal = skipUnauth ?? false;
|
|
2947
|
-
const
|
|
2948
|
-
const
|
|
3285
|
+
const factoryEnabled = () => infiniteOptions.enabled;
|
|
3286
|
+
const isUnauthorized = createMemo(() => authType === "required" && !safeAuth.isLoading && !safeAuth.isAuthenticated);
|
|
3287
|
+
const shouldSkip = createMemo(() => factoryEnabled() === false || authType === "required" && safeAuth.isLoading || authType === "required" && !safeAuth.isAuthenticated);
|
|
2949
3288
|
const authError = createMemo(() => {
|
|
2950
|
-
if (isUnauthorized && !skipUnauthFinal) return new CRPCClientError({
|
|
3289
|
+
if (isUnauthorized() && !skipUnauthFinal) return new CRPCClientError({
|
|
2951
3290
|
code: "UNAUTHORIZED",
|
|
2952
3291
|
functionName: queryName
|
|
2953
3292
|
});
|
|
2954
3293
|
return null;
|
|
2955
3294
|
});
|
|
2956
3295
|
createEffect(() => {
|
|
2957
|
-
if (isUnauthorized && !skipUnauthFinal) onQueryUnauthorized({ queryName });
|
|
3296
|
+
if (isUnauthorized() && !skipUnauthFinal) onQueryUnauthorized({ queryName });
|
|
2958
3297
|
});
|
|
2959
3298
|
const result = useInfiniteQueryInternal(query, args, {
|
|
3299
|
+
authType,
|
|
2960
3300
|
limit,
|
|
2961
3301
|
...queryOptions,
|
|
2962
|
-
enabled: !shouldSkip
|
|
3302
|
+
enabled: () => resolveEnabled(!shouldSkip(), factoryEnabled())
|
|
2963
3303
|
});
|
|
2964
3304
|
const authLoadingApplies = authType === "optional" || authType === "required";
|
|
2965
|
-
const isSkippedUnauth = isUnauthorized && skipUnauthFinal;
|
|
3305
|
+
const isSkippedUnauth = createMemo(() => isUnauthorized() && skipUnauthFinal);
|
|
2966
3306
|
return {
|
|
2967
3307
|
get data() {
|
|
2968
|
-
return isSkippedUnauth ? [] : result.data;
|
|
3308
|
+
return isSkippedUnauth() ? [] : result.data;
|
|
2969
3309
|
},
|
|
2970
3310
|
get pages() {
|
|
2971
|
-
return isSkippedUnauth ? [] : result.pages;
|
|
3311
|
+
return isSkippedUnauth() ? [] : result.pages;
|
|
2972
3312
|
},
|
|
2973
3313
|
get error() {
|
|
2974
3314
|
return authError() ?? result.error;
|
|
@@ -2977,12 +3317,12 @@ function useInfiniteQuery(infiniteOptions) {
|
|
|
2977
3317
|
return authError() ? true : result.isError;
|
|
2978
3318
|
},
|
|
2979
3319
|
get isPlaceholderData() {
|
|
2980
|
-
return isSkippedUnauth ? false : result.isPlaceholderData;
|
|
3320
|
+
return isSkippedUnauth() ? false : result.isPlaceholderData;
|
|
2981
3321
|
},
|
|
2982
3322
|
get isLoading() {
|
|
2983
3323
|
const ae = authError();
|
|
2984
3324
|
const isClientError = isCRPCClientError(result.error);
|
|
2985
|
-
return authLoadingApplies && safeAuth.isLoading || !isClientError && !ae && !isSkippedUnauth && result.isLoading;
|
|
3325
|
+
return authLoadingApplies && safeAuth.isLoading || !isClientError && !ae && !isSkippedUnauth() && result.isLoading;
|
|
2986
3326
|
},
|
|
2987
3327
|
get isFetching() {
|
|
2988
3328
|
return result.isFetching;
|