kitcn 0.17.4 → 0.18.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.
@@ -23,6 +23,27 @@ function isFieldReference(value) {
23
23
  return value && typeof value === "object" && value.__brand === "FieldReference";
24
24
  }
25
25
  /**
26
+ * The pattern is the same for every row of a scan while the value is not, so
27
+ * only the value genuinely has to be split per call. A small bounded cache
28
+ * keeps the split pattern across rows without unbounded growth on
29
+ * caller-supplied patterns; a query with a handful of LIKE filters still hits
30
+ * on every row.
31
+ */
32
+ const LIKE_PATTERN_CACHE_MAX = 16;
33
+ const likePatternCache = /* @__PURE__ */ new Map();
34
+ function likePatternCodePoints(pattern, caseInsensitive) {
35
+ const key = caseInsensitive ? `i:${pattern}` : `s:${pattern}`;
36
+ const cached = likePatternCache.get(key);
37
+ if (cached) return cached;
38
+ const source = Array.from(caseInsensitive ? pattern.toLowerCase() : pattern);
39
+ if (likePatternCache.size >= LIKE_PATTERN_CACHE_MAX) for (const oldest of likePatternCache.keys()) {
40
+ likePatternCache.delete(oldest);
41
+ break;
42
+ }
43
+ likePatternCache.set(key, source);
44
+ return source;
45
+ }
46
+ /**
26
47
  * SQL `LIKE` semantics: `%` matches any run of characters, `_` matches exactly
27
48
  * one, everything else is literal. Wildcards work anywhere in the pattern, not
28
49
  * only at the ends.
@@ -36,7 +57,7 @@ function isFieldReference(value) {
36
57
  */
37
58
  function matchLikePattern(value, pattern, caseInsensitive) {
38
59
  const target = Array.from(caseInsensitive ? value.toLowerCase() : value);
39
- const source = Array.from(caseInsensitive ? pattern.toLowerCase() : pattern);
60
+ const source = likePatternCodePoints(pattern, caseInsensitive);
40
61
  let valueIndex = 0;
41
62
  let patternIndex = 0;
42
63
  let wildcardPatternIndex = -1;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { A as useSafeConvexAuth, C as useAuthStore, D as useFetchAccessToken, E as useConvexAuthRecovery, O as useIsAuth, S as useAuthState, T as useConvexAuthBridge, _ as Unauthenticated, a as Authenticated, b as useAuth, c as ConvexAuthRecoveryError, d as ConvexAuthRecoveryStatus, f as ConvexProviderWithAuth, g as MaybeUnauthenticated, h as MaybeAuthenticated, i as AuthStoreState, k as useMaybeAuth, 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 decodeJwtExp, w as useAuthValue, x as useAuthGuard, y as isSessionSyncGraceActive } from "../auth-store-GDNKweSK.js";
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-47WTg13B.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";
@@ -236,7 +236,6 @@ declare class ConvexQueryClient {
236
236
  /** Unsubscribe a live Convex watch (if present) and remove it from the subscription map. */
237
237
  private unsubscribeQueryByHash;
238
238
  private isAuthBoundQuery;
239
- private isQueryDisabled;
240
239
  private subscribeQuery;
241
240
  /** Update auth store (for HMR where jotai store may reset) */
242
241
  updateAuthStore(authStore?: AuthStore): void;
@@ -986,6 +985,12 @@ declare function useInfiniteQuery<T extends FunctionReference<'query'>, TItem =
986
985
  //#region src/react/use-query-options.d.ts
987
986
  type ReservedQueryOptions = 'queryKey' | 'queryFn' | 'staleTime';
988
987
  type ReservedMutationOptions = 'mutationFn';
988
+ type QueryOptionsResult<T extends FunctionReference<'query'>> = ConvexQueryOptions<T> & {
989
+ meta: ConvexQueryMeta;
990
+ };
991
+ type ActionQueryOptionsResult<T extends FunctionReference<'action'>> = ConvexActionOptions<T> & {
992
+ meta: ConvexQueryMeta;
993
+ };
989
994
  /**
990
995
  * Hook that returns query options for use with useQuery.
991
996
  * Handles skipUnauth by setting enabled: false when unauthorized.
@@ -1010,9 +1015,7 @@ type ReservedMutationOptions = 'mutationFn';
1010
1015
  * const { data } = useQuery(useConvexQueryOptions(api.user.get, { id }, { enabled: !!id, placeholderData: [] }));
1011
1016
  * ```
1012
1017
  */
1013
- declare function useConvexQueryOptions<T extends FunctionReference<'query'>>(funcRef: T, args: FunctionArgs<T> | SkipToken, options?: ConvexQueryHookOptions & DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedQueryOptions>): ConvexQueryOptions<T> & {
1014
- meta: ConvexQueryMeta;
1015
- };
1018
+ declare function useConvexQueryOptions<T extends FunctionReference<'query'>>(funcRef: T, args: FunctionArgs<T> | SkipToken, options?: ConvexQueryHookOptions & DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedQueryOptions>): QueryOptionsResult<T>;
1016
1019
  /**
1017
1020
  * Hook that returns infinite query options for use with useInfiniteQuery.
1018
1021
  * Handles auth type detection from meta and skipUnauth.
@@ -1060,9 +1063,7 @@ declare function useConvexInfiniteQueryOptions<T extends FunctionReference<'quer
1060
1063
  */
1061
1064
  declare function useConvexActionQueryOptions<Action extends FunctionReference<'action'>>(action: Action, args: FunctionArgs<Action> | SkipToken, options?: {
1062
1065
  skipUnauth?: boolean;
1063
- } & DistributiveOmit<UseQueryOptions<FunctionReturnType<Action>, DefaultError>, ReservedQueryOptions>): ConvexActionOptions<Action> & {
1064
- meta: ConvexQueryMeta;
1065
- };
1066
+ } & DistributiveOmit<UseQueryOptions<FunctionReturnType<Action>, DefaultError>, ReservedQueryOptions>): ActionQueryOptionsResult<Action>;
1066
1067
  /**
1067
1068
  * Hook that returns mutation options for use with useMutation.
1068
1069
  * Wraps the Convex mutation with auth guard logic.
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { A as defaultIsUnauthorized, C as useSafeConvexAuth, D as writeAuthSessionFallbackData, O as writeAuthSessionFallbackToken, S as useMaybeAuth, _ as useAuthValue, a as ConvexAuthRecoveryError, b as useFetchAccessToken, c as MaybeAuthenticated, d as decodeJwtExp, f as isSessionSyncGraceActive, g as useAuthStore, h as useAuthState, i as ConvexAuthBridge, j as isCRPCClientError, k as CRPCClientError, l as MaybeUnauthenticated, m as useAuthGuard, n as AuthProvider, o as ConvexProviderWithAuth, p as useAuth, r as Authenticated, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS, u as Unauthenticated, v as useConvexAuthBridge, w as clearAuthSessionFallback, x as useIsAuth, y as useConvexAuthRecovery } from "../auth-store-BnGZxmnY.js";
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-BHk8eMnX.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";
@@ -12,7 +12,7 @@ import { ConvexHttpClient } from "convex/browser";
12
12
  //#region src/shared/meta-utils.ts
13
13
  const metaCache = /* @__PURE__ */ new WeakMap();
14
14
  const nonMetaLeafKeys = new Set(["functionRef", "ref"]);
15
- function isRecord(value) {
15
+ function isRecord$1(value) {
16
16
  return typeof value === "object" && value !== null;
17
17
  }
18
18
  function isFunctionType(value) {
@@ -34,10 +34,10 @@ function extractLeafMeta(value) {
34
34
  }
35
35
  function getHttpRoutes(api) {
36
36
  const routes = api._http;
37
- if (!isRecord(routes)) return;
37
+ if (!isRecord$1(routes)) return;
38
38
  const normalized = {};
39
39
  for (const [routeKey, routeValue] of Object.entries(routes)) {
40
- if (!isRecord(routeValue)) continue;
40
+ if (!isRecord$1(routeValue)) continue;
41
41
  const routePath = routeValue.path;
42
42
  const routeMethod = routeValue.method;
43
43
  if (typeof routePath === "string" && typeof routeMethod === "string") normalized[routeKey] = {
@@ -60,7 +60,7 @@ function buildMetaIndex(api) {
60
60
  const walk = (node, path) => {
61
61
  for (const [key, value] of Object.entries(node)) {
62
62
  if (key.startsWith("_")) continue;
63
- if (!isRecord(value)) continue;
63
+ if (!isRecord$1(value)) continue;
64
64
  const leafMeta = extractLeafMeta(value);
65
65
  if (leafMeta) {
66
66
  if (path.length === 0) continue;
@@ -798,6 +798,49 @@ function createHashFn(fallback = hashKey) {
798
798
  };
799
799
  }
800
800
 
801
+ //#endregion
802
+ //#region src/internal/shallow.ts
803
+ /**
804
+ * Framework-free shallow comparison helpers.
805
+ *
806
+ * The bindings use these to keep object identity stable across renders when
807
+ * nothing observable changed, so TanStack Query's `shallowEqualObjects` guards
808
+ * and React dependency arrays can actually hit.
809
+ */
810
+ /** True when `value` is a non-null object. */
811
+ function isRecord(value) {
812
+ return typeof value === "object" && value !== null;
813
+ }
814
+ /**
815
+ * Compare own enumerable string keys with `Object.is`.
816
+ *
817
+ * Deliberately shallow, never deep: option values are frequently closures
818
+ * (`select`, `retry`, function-form `placeholderData`) that capture fresh
819
+ * render scope, and treating a stale closure as equal would serve stale data.
820
+ */
821
+ function isShallowEqual(a, b) {
822
+ if (a === b) return true;
823
+ const keys = Object.keys(a);
824
+ if (keys.length !== Object.keys(b).length) return false;
825
+ const left = a;
826
+ const right = b;
827
+ for (const key of keys) {
828
+ if (!Object.hasOwn(right, key)) return false;
829
+ if (!Object.is(left[key], right[key])) return false;
830
+ }
831
+ return true;
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/internal/use-stable-identity.ts
836
+ function useStableIdentity(value, isEqual = isShallowEqual) {
837
+ const previousRef = useRef(null);
838
+ const previous = previousRef.current;
839
+ if (previous !== null && isEqual(previous, value)) return previous;
840
+ previousRef.current = value;
841
+ return value;
842
+ }
843
+
801
844
  //#endregion
802
845
  //#region src/react/use-query-options.ts
803
846
  /** biome-ignore-all lint/suspicious/noExplicitAny: Convex type compatibility */
@@ -807,22 +850,28 @@ function createHashFn(fallback = hashKey) {
807
850
  */
808
851
  const EMPTY_ARGS = {};
809
852
  const hashConvexOptionsKey = createHashFn();
810
- const MAX_STABLE_ARGS = 500;
811
- const stableArgsByHash = /* @__PURE__ */ new Map();
812
- function getStableArgsByHash(hash, args) {
813
- if (stableArgsByHash.has(hash)) {
814
- const stableArgs = stableArgsByHash.get(hash);
815
- stableArgsByHash.delete(hash);
816
- stableArgsByHash.set(hash, stableArgs);
817
- return stableArgs;
818
- }
819
- const stored = structuredClone(args);
820
- stableArgsByHash.set(hash, stored);
821
- if (stableArgsByHash.size > MAX_STABLE_ARGS) {
822
- const oldestHash = stableArgsByHash.keys().next().value;
823
- if (oldestHash !== void 0) stableArgsByHash.delete(oldestHash);
853
+ /**
854
+ * Compare two computed option objects for observable equivalence.
855
+ *
856
+ * Top-level values are compared by reference so inline `select` /
857
+ * `placeholderData` closures are never treated as equal. `meta` is exempt: it
858
+ * is rebuilt from scalars by these hooks on every render, so comparing its
859
+ * identity would defeat the whole check.
860
+ */
861
+ function areQueryOptionsEquivalent(a, b) {
862
+ const keys = Object.keys(a);
863
+ if (keys.length !== Object.keys(b).length) return false;
864
+ const left = a;
865
+ const right = b;
866
+ for (const key of keys) {
867
+ if (!Object.hasOwn(right, key)) return false;
868
+ const previous = left[key];
869
+ const next = right[key];
870
+ if (Object.is(previous, next)) continue;
871
+ if (key === "meta" && isRecord(previous) && isRecord(next) && isShallowEqual(previous, next)) continue;
872
+ return false;
824
873
  }
825
- return stored;
874
+ return true;
826
875
  }
827
876
  function useStableQueryArgs(prefix, funcRef, args) {
828
877
  const resolvedArgs = args === skipToken || args == null ? EMPTY_ARGS : args;
@@ -831,7 +880,7 @@ function useStableQueryArgs(prefix, funcRef, args) {
831
880
  getFunctionName(funcRef),
832
881
  resolvedArgs
833
882
  ]);
834
- const value = useMemo(() => getStableArgsByHash(argsHash, resolvedArgs), [argsHash, resolvedArgs]);
883
+ const value = useMemo(() => structuredClone(resolvedArgs), [argsHash]);
835
884
  return useMemo(() => ({
836
885
  hash: argsHash,
837
886
  value
@@ -870,7 +919,7 @@ function useConvexQueryOptions(funcRef, args, options) {
870
919
  });
871
920
  const stableArgs = useStableQueryArgs("convexQuery", funcRef, isSkipped ? EMPTY_ARGS : args);
872
921
  const baseOptions = useMemo(() => convexQuery(funcRef, stableArgs.value), [funcRef, stableArgs]);
873
- return useMemo(() => {
922
+ return useStableIdentity(useMemo(() => {
874
923
  const { enabled: userEnabled, skipUnauth, subscribe, ...queryOptions } = options ?? {};
875
924
  return {
876
925
  ...baseOptions,
@@ -889,7 +938,7 @@ function useConvexQueryOptions(funcRef, args, options) {
889
938
  isSkipped,
890
939
  options,
891
940
  shouldSkip
892
- ]);
941
+ ]), areQueryOptionsEquivalent);
893
942
  }
894
943
  /**
895
944
  * Hook that returns infinite query options for use with useInfiniteQuery.
@@ -965,7 +1014,7 @@ function useConvexActionQueryOptions(action, args, options) {
965
1014
  });
966
1015
  const stableArgs = useStableQueryArgs("convexAction", action, isSkipped ? EMPTY_ARGS : args);
967
1016
  const baseOptions = useMemo(() => convexAction(action, stableArgs.value), [action, stableArgs]);
968
- return useMemo(() => {
1017
+ return useStableIdentity(useMemo(() => {
969
1018
  const { enabled: userEnabled, skipUnauth, ...queryOptions } = options ?? {};
970
1019
  return {
971
1020
  ...baseOptions,
@@ -983,7 +1032,7 @@ function useConvexActionQueryOptions(action, args, options) {
983
1032
  isSkipped,
984
1033
  options,
985
1034
  shouldSkip
986
- ]);
1035
+ ]), areQueryOptionsEquivalent);
987
1036
  }
988
1037
  /**
989
1038
  * Hook that returns mutation options for use with useMutation.
@@ -1280,6 +1329,17 @@ function useFnMeta() {
1280
1329
  return (namespace, fnName) => meta?.[namespace]?.[fnName];
1281
1330
  }
1282
1331
  /**
1332
+ * Stable signature of the current auth identity.
1333
+ *
1334
+ * JWTs collapse to their non-volatile claims so a refresh is a no-op; anything
1335
+ * that is not a JWT keeps its own signature so opaque-token transitions and
1336
+ * logout still register.
1337
+ */
1338
+ function resolveAuthIdentity(token) {
1339
+ if (token === null) return null;
1340
+ return decodeJwtIdentity(token) ?? `opaque:${token}`;
1341
+ }
1342
+ /**
1283
1343
  * Create CRPC context, provider, and hooks for a Convex API.
1284
1344
  *
1285
1345
  * @param options - Configuration object containing api and optional HTTP settings
@@ -1312,7 +1372,6 @@ function createCRPCContext(options) {
1312
1372
  const meta = buildMetaIndex(api);
1313
1373
  const CRPCProxyContext = createContext(null);
1314
1374
  const VanillaClientContext = createContext(null);
1315
- const HttpProxyContext = createContext(void 0);
1316
1375
  /** Inner provider */
1317
1376
  function CRPCProviderInner({ children, convexClient, convexQueryClient }) {
1318
1377
  const authStore = useAuthStore();
@@ -1323,12 +1382,13 @@ function createCRPCContext(options) {
1323
1382
  useEffect(() => {
1324
1383
  const previous = previousAuthRef.current;
1325
1384
  const tokenReady = token === null || decodeJwtExp(token) !== null;
1385
+ const identity = resolveAuthIdentity(token);
1326
1386
  previousAuthRef.current = {
1327
1387
  isAuthenticated,
1328
- token
1388
+ identity
1329
1389
  };
1330
1390
  if (!previous) return;
1331
- if (tokenReady && (previous.token !== token || previous.isAuthenticated !== isAuthenticated)) convexQueryClient.resetAuthQueries();
1391
+ if (tokenReady && (previous.identity !== identity || previous.isAuthenticated !== isAuthenticated)) convexQueryClient.resetAuthQueries();
1332
1392
  }, [
1333
1393
  convexQueryClient,
1334
1394
  isAuthenticated,
@@ -1363,18 +1423,29 @@ function createCRPCContext(options) {
1363
1423
  }, [authStore, fetchAccessToken]);
1364
1424
  const proxy = useMemo(() => createCRPCOptionsProxy(api, meta, options.transformer), []);
1365
1425
  const vanillaClient = useMemo(() => createVanillaCRPCProxy(api, meta, convexClient, options.transformer), [convexClient]);
1426
+ const mergedProxy = useMemo(() => {
1427
+ if (!httpProxy) return proxy;
1428
+ return new Proxy(proxy, { get(target, prop) {
1429
+ if (prop === "http") return httpProxy;
1430
+ return Reflect.get(target, prop);
1431
+ } });
1432
+ }, [httpProxy, proxy]);
1433
+ const mergedClient = useMemo(() => {
1434
+ if (!httpProxy) return vanillaClient;
1435
+ return new Proxy(vanillaClient, { get(target, prop) {
1436
+ if (prop === "http") return httpProxy;
1437
+ return Reflect.get(target, prop);
1438
+ } });
1439
+ }, [httpProxy, vanillaClient]);
1366
1440
  return /* @__PURE__ */ jsx(ConvexQueryClientContext.Provider, {
1367
1441
  value: convexQueryClient,
1368
1442
  children: /* @__PURE__ */ jsx(MetaContext.Provider, {
1369
1443
  value: meta,
1370
1444
  children: /* @__PURE__ */ jsx(VanillaClientContext.Provider, {
1371
- value: vanillaClient,
1372
- children: /* @__PURE__ */ jsx(HttpProxyContext.Provider, {
1373
- value: httpProxy,
1374
- children: /* @__PURE__ */ jsx(CRPCProxyContext.Provider, {
1375
- value: proxy,
1376
- children
1377
- })
1445
+ value: mergedClient,
1446
+ children: /* @__PURE__ */ jsx(CRPCProxyContext.Provider, {
1447
+ value: mergedProxy,
1448
+ children
1378
1449
  })
1379
1450
  })
1380
1451
  })
@@ -1408,12 +1479,7 @@ function createCRPCContext(options) {
1408
1479
  */
1409
1480
  function useCRPC() {
1410
1481
  const ctx = useContext(CRPCProxyContext);
1411
- const httpProxy = useContext(HttpProxyContext);
1412
1482
  if (!ctx) throw new Error("useCRPC must be used within CRPCProvider");
1413
- if (httpProxy) return new Proxy(ctx, { get(target, prop) {
1414
- if (prop === "http") return httpProxy;
1415
- return Reflect.get(target, prop);
1416
- } });
1417
1483
  return ctx;
1418
1484
  }
1419
1485
  /**
@@ -1436,12 +1502,7 @@ function createCRPCContext(options) {
1436
1502
  */
1437
1503
  function useCRPCClient() {
1438
1504
  const ctx = useContext(VanillaClientContext);
1439
- const httpProxy = useContext(HttpProxyContext);
1440
1505
  if (!ctx) throw new Error("useCRPCClient must be used within CRPCProvider");
1441
- if (httpProxy) return new Proxy(ctx, { get(target, prop) {
1442
- if (prop === "http") return httpProxy;
1443
- return Reflect.get(target, prop);
1444
- } });
1445
1506
  return ctx;
1446
1507
  }
1447
1508
  return {
@@ -1720,6 +1781,19 @@ function isConvexAction(queryKey) {
1720
1781
  return queryKey.length >= 2 && queryKey[0] === "convexAction";
1721
1782
  }
1722
1783
  /**
1784
+ * Write a subscription value into an already-resolved Query.
1785
+ *
1786
+ * Going through the Query skips the query-key re-hash that the key-based
1787
+ * `getQueryData` / `setQueryData` pair pays on every push. `setQueryData`
1788
+ * short-circuits on `undefined` and `Query.setData` does not, so the guard has
1789
+ * to be reproduced here: without it a never-resolved subscription would flip to
1790
+ * `status: 'success'` with `data: undefined`.
1791
+ */
1792
+ function writeQueryData(query, value) {
1793
+ if (value === void 0) return;
1794
+ query.setData(value, { manual: true });
1795
+ }
1796
+ /**
1723
1797
  * Bridges TanStack Query with Convex real-time subscriptions.
1724
1798
  *
1725
1799
  * ## Setup
@@ -1812,15 +1886,12 @@ var ConvexQueryClient = class {
1812
1886
  const meta = query.meta;
1813
1887
  return meta?.authType === "required" || meta?.authType === "optional";
1814
1888
  }
1815
- isQueryDisabled(query) {
1816
- return query.isDisabled();
1817
- }
1818
1889
  subscribeQuery(query) {
1819
1890
  if (this.subscriptions[query.queryHash]) return;
1820
1891
  const meta = query.meta;
1821
1892
  if (meta?.subscribe === false) return;
1822
1893
  if (query.getObserversCount() === 0) return;
1823
- if (this.isQueryDisabled(query)) return;
1894
+ if (query.isDisabled()) return;
1824
1895
  if (this.shouldSkipSubscription(meta?.authType)) return;
1825
1896
  const [, funcName, args] = query.queryKey;
1826
1897
  const watch = this.convexClient.watchQuery(funcName, this.transformer.input.serialize(args));
@@ -1958,15 +2029,15 @@ var ConvexQueryClient = class {
1958
2029
  };
1959
2030
  }
1960
2031
  if (result.ok) {
1961
- const existingData = this.queryClient.getQueryData(queryKey);
1962
- if (result.value !== void 0 || !(existingData !== void 0)) this.queryClient.setQueryData(queryKey, this.transformer.output.deserialize(result.value));
2032
+ const existingData = query.state.data;
2033
+ if (result.value !== void 0 || !(existingData !== void 0)) writeQueryData(query, this.transformer.output.deserialize(result.value));
1963
2034
  } else {
1964
2035
  const { error } = result;
1965
2036
  const authState = this.getAuthState();
1966
2037
  const meta = query.meta;
1967
2038
  const isUnauthorized = authState?.isUnauthorized(error) ?? false;
1968
2039
  if (isUnauthorized && meta?.skipUnauth) {
1969
- this.queryClient.setQueryData(queryKey, this.transformer.output.deserialize(null));
2040
+ writeQueryData(query, this.transformer.output.deserialize(null));
1970
2041
  return;
1971
2042
  }
1972
2043
  query.setState({
@@ -2023,7 +2094,7 @@ var ConvexQueryClient = class {
2023
2094
  if (event.action.type === "setState" && event.action.setStateOptions?.meta === "set by ConvexQueryClient") break;
2024
2095
  break;
2025
2096
  case "observerOptionsUpdated": {
2026
- const isDisabled = this.isQueryDisabled(event.query);
2097
+ const isDisabled = event.query.isDisabled();
2027
2098
  const isSubscribed = !!this.subscriptions[event.query.queryHash];
2028
2099
  if (isDisabled && isSubscribed) {
2029
2100
  this.cancelPendingUnsubscribe(event.query.queryHash);
@@ -2289,7 +2360,8 @@ const useStaleCursorRecovery = ({ argsObject, combined, limit, setState, state }
2289
2360
  * Use `useInfiniteQuery` for the public API with auth handling.
2290
2361
  */
2291
2362
  const useInfiniteQueryInternal = (query, args, options) => {
2292
- const { limit, enabled, placeholderData, ...queryOptions } = options;
2363
+ const { limit, enabled, placeholderData, ...forwardedOptions } = options;
2364
+ const queryOptions = useStableIdentity(forwardedOptions);
2293
2365
  const { isLoading: isAuthLoading } = useSafeConvexAuth();
2294
2366
  const meta = useMeta();
2295
2367
  const queryClient = useQueryClient();
@@ -2433,7 +2505,7 @@ const useInfiniteQueryInternal = (query, args, options) => {
2433
2505
  prefetchedFirstPage,
2434
2506
  placeholderData
2435
2507
  ]),
2436
- combine: (results) => {
2508
+ combine: useCallback((results) => {
2437
2509
  const allItems = [];
2438
2510
  const pages = [];
2439
2511
  const seenIds = /* @__PURE__ */ new Set();
@@ -2474,7 +2546,7 @@ const useInfiniteQueryInternal = (query, args, options) => {
2474
2546
  isRefetching: isFetching && allItems.length > 0 && !isPlaceholderData,
2475
2547
  _rawResults: results
2476
2548
  };
2477
- }
2549
+ }, [placeholderData])
2478
2550
  });
2479
2551
  useStaleCursorRecovery({
2480
2552
  argsObject,
@@ -2556,6 +2628,13 @@ const useInfiniteQueryInternal = (query, args, options) => {
2556
2628
  setState,
2557
2629
  argsObject
2558
2630
  ]);
2631
+ const loadMoreRef = useRef(loadMore);
2632
+ const limitRef = useRef(limit);
2633
+ useEffect(() => {
2634
+ loadMoreRef.current = loadMore;
2635
+ limitRef.current = limit;
2636
+ }, [loadMore, limit]);
2637
+ const fetchNextPage = useCallback((n) => loadMoreRef.current(n ?? limitRef.current), []);
2559
2638
  const { _rawResults, lastPage, ...result } = combined;
2560
2639
  const hasNextPage = combined.status === "CanLoadMore";
2561
2640
  const isFetchingNextPage = combined.status === "LoadingMore";
@@ -2563,7 +2642,7 @@ const useInfiniteQueryInternal = (query, args, options) => {
2563
2642
  ...result,
2564
2643
  failureReason: combined.failureReason,
2565
2644
  error: combined.error instanceof Error ? combined.error : null,
2566
- fetchNextPage: (n) => loadMore(n ?? limit),
2645
+ fetchNextPage,
2567
2646
  hasNextPage,
2568
2647
  isFetchingNextPage
2569
2648
  };
@@ -2608,10 +2687,11 @@ function useInfiniteQuery(infiniteOptions) {
2608
2687
  queryName,
2609
2688
  onQueryUnauthorized
2610
2689
  ]);
2690
+ const enabled = useMemo(() => resolveEnabled(!shouldSkip, factoryEnabled), [shouldSkip, factoryEnabled]);
2611
2691
  const result = useInfiniteQueryInternal(query, args, {
2612
2692
  limit,
2613
2693
  ...queryOptions,
2614
- enabled: resolveEnabled(!shouldSkip, factoryEnabled)
2694
+ enabled
2615
2695
  });
2616
2696
  const authLoadingApplies = authType === "optional" || authType === "required";
2617
2697
  const isClientError = isCRPCClientError(result.error);