kitcn 0.20.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.
@@ -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
- return useAuthStore().get(key);
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
- createEffect(() => {
1024
- const auth = props.useAuth();
1025
- const loading = auth.isLoading;
1026
- const authenticated = auth.isAuthenticated;
1027
- if (loading) return;
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
- setIsConvexAuthenticated(false);
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
- client.setAuth(auth.fetchAccessToken, (isAuth) => {
1035
- setIsConvexLoading(false);
1036
- setIsConvexAuthenticated(isAuth);
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 { skipUnauth: _, subscribe, ...queryOptions } = options ?? {};
1176
+ const { enabled: userEnabled, skipUnauth, subscribe, ...queryOptions } = options ?? {};
1114
1177
  return {
1115
1178
  ...baseOptions,
1116
1179
  ...queryOptions,
1117
- enabled: isSkipped ? false : !authSkip.shouldSkip,
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 { skipUnauth: _, ...queryOptions } = options ?? {};
1264
+ const { enabled: userEnabled, skipUnauth, ...queryOptions } = options ?? {};
1199
1265
  return {
1200
1266
  ...baseOptions,
1201
1267
  ...queryOptions,
1202
- enabled: isSkipped ? false : !authSkip.shouldSkip
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 || !this.authStore) return false;
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
- const meta = event.query.meta;
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
- if (this.subscriptions[event.query.queryHash]) break;
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.options.enabled === false;
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
- const meta = event.query.meta;
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
- if (!hasActiveSessionData(currentSession)) {
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
- pendingTokenPromise = props.authClient.convex.token({ fetchOptions }).then((result) => {
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 = null;
2703
+ if (pendingTokenPromise === tokenPromise) {
2704
+ pendingTokenPromise = null;
2705
+ pendingTokenSessionId = void 0;
2706
+ }
2385
2707
  });
2386
- return pendingTokenPromise;
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 getOrCreatePaginationId = (storeKey) => {
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 = getOrCreatePaginationId(storeKey());
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: factoryEnabled, meta, ...queryOptions } = infiniteOptions;
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 isUnauthorized = authType === "required" && !safeAuth.isLoading && !safeAuth.isAuthenticated;
2948
- const shouldSkip = factoryEnabled === false || authType === "required" && safeAuth.isLoading || authType === "required" && !safeAuth.isAuthenticated;
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;