kitcn 0.33.4 → 0.33.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # kitcn
2
2
 
3
+ ## 0.33.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#473](https://github.com/udecode/kitcn/pull/473) [`16e0bb4`](https://github.com/udecode/kitcn/commit/16e0bb46d66a4d7166d577793fcb47f8161efa5d) Thanks [@EfficiencyCorp](https://github.com/EfficiencyCorp)! - - Add opt-in optimistic auth and document identity admission to
8
+ `ConvexAuthProvider`, with the same guarded token source for Convex and cRPC
9
+ HTTP requests.
10
+ - Add Convex-native `optimisticUpdate` support to cRPC `mutationOptions`.
11
+ - Reuse the Convex client's logger for server HTTP clients so construction is
12
+ deterministic during prerendering.
13
+
14
+ ## 0.33.5
15
+
16
+ ### Patch Changes
17
+
18
+ - [#471](https://github.com/udecode/kitcn/pull/471) [`04346ae`](https://github.com/udecode/kitcn/commit/04346ae4c7f207f2e57eed56bffb3100472fb649) Thanks [@zbeyens](https://github.com/zbeyens)! - ## Patches
19
+
20
+ - Improve generated procedure-name lookups with merge-local entries and stale-state warnings.
21
+
3
22
  ## 0.33.4
4
23
 
5
24
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-Bxzoofl4.js";
2
- import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-D2ifnjjj.js";
2
+ import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-BzAJnWt4.js";
3
3
  import * as convex_values0 from "convex/values";
4
4
  import { GenericId, Infer, Value } from "convex/values";
5
5
  import { DocumentByName, GenericDataModel, GenericDatabaseReader, GenericDatabaseWriter, TableNamesInDataModel } from "convex/server";
@@ -398,6 +398,51 @@ type ConvexAuthProviderProps = {
398
398
  queryName: string;
399
399
  }) => void; /** Custom function to detect UNAUTHORIZED errors. Default checks code property. */
400
400
  isUnauthorized?: (error: unknown) => boolean;
401
+ /**
402
+ * Run auth-bound queries as soon as an unexpired JWT is held instead of
403
+ * after Convex confirms it. Convex sends them after Authenticate on the same
404
+ * socket and evaluates none of them if the token is refused; a refused token
405
+ * sets `isAuthenticated` back to false, which resets auth-bound queries, and
406
+ * never opens the gate again. Default `false`.
407
+ */
408
+ optimisticAuth?: boolean;
409
+ /**
410
+ * Fix the identity (JWT `sub` and `sessionId`) the document speaks for: the
411
+ * one it holds when it mounts (`initialToken`), or, with none, the first
412
+ * token it obtains. A token for another user or session is refused before
413
+ * it is cached, so neither Convex, the optimistic gate nor HTTP headers ever
414
+ * see it: the Convex client is closed (dropping queued requests and
415
+ * optimistic updates), every later token request answers null, and this is
416
+ * called, typically to reload the page. Same-session refreshes pass
417
+ * through. Off when not set.
418
+ */
419
+ onTokenIdentityChange?: () => void;
420
+ /**
421
+ * The identity (`sub|sessionId`, as the guard decodes it from a JWT) the
422
+ * document already speaks for when this provider mounts, for an app that
423
+ * mounts the provider more than once in one document (for example one per
424
+ * route group, over a shared Convex client). The guard starts from it
425
+ * instead of from `initialToken` or the first token obtained, so a remount
426
+ * without a token still refuses another user's or session's token.
427
+ *
428
+ * A fixed value is read on the first render only. A getter is read at
429
+ * every admission, cached tokens included, and a token must match both the
430
+ * identity it returns and the one this guard already admitted; any
431
+ * mismatch trips the guard. So a provider kept mounted but hidden (React
432
+ * `<Activity>`) cannot resume under an identity the document has since
433
+ * moved away from. A getter returning null does not constrain. Needs
434
+ * `onTokenIdentityChange`.
435
+ */
436
+ tokenIdentityBaseline?: string | null | (() => string | null);
437
+ /**
438
+ * Called synchronously with every token the identity guard admits as it is
439
+ * handed to Convex or HTTP headers, cached tokens included, so the document
440
+ * can claim the identity at that moment (for example the first token a
441
+ * document without one obtains). Never called for a refused token. Read
442
+ * from a ref, so passing a new function does not re-run effects. Needs
443
+ * `onTokenIdentityChange`.
444
+ */
445
+ onTokenIdentityAdmitted?: (token: string) => void;
401
446
  };
402
447
  /**
403
448
  * Unified auth provider for Convex + Better Auth.
@@ -414,7 +459,11 @@ declare function ConvexAuthProvider({
414
459
  initialToken,
415
460
  onMutationUnauthorized,
416
461
  onQueryUnauthorized,
417
- isUnauthorized
462
+ isUnauthorized,
463
+ optimisticAuth,
464
+ onTokenIdentityChange,
465
+ onTokenIdentityAdmitted,
466
+ tokenIdentityBaseline
418
467
  }: ConvexAuthProviderProps): react_jsx_runtime0.JSX.Element;
419
468
  //#endregion
420
469
  export { type AuthClient, type AuthClientWithPlugins, ConvexAuthProvider, type ConvexAuthProviderClient, ConvexAuthProviderProps, ConvexAuthProviderQueryClient, type PluginsWithCrossDomain, type PluginsWithoutCrossDomain, convexClient };
@@ -134,7 +134,7 @@ const clearSessionAtom = (authClient) => {
134
134
  * Structure: AuthProvider wraps ConvexAuthProviderInner so that
135
135
  * useAuthStore() is available when creating fetchAccessToken.
136
136
  */
137
- function ConvexAuthProvider({ children, client, authClient, convexQueryClient, initialToken, onMutationUnauthorized, onQueryUnauthorized, isUnauthorized }) {
137
+ function ConvexAuthProvider({ children, client, authClient, convexQueryClient, initialToken, onMutationUnauthorized, onQueryUnauthorized, isUnauthorized, optimisticAuth = false, onTokenIdentityChange, onTokenIdentityAdmitted, tokenIdentityBaseline }) {
138
138
  useOTTHandler(authClient);
139
139
  return /* @__PURE__ */ jsx(AuthProvider, {
140
140
  initialValues: useMemo(() => ({
@@ -148,6 +148,10 @@ function ConvexAuthProvider({ children, client, authClient, convexQueryClient, i
148
148
  authClient,
149
149
  client,
150
150
  convexQueryClient,
151
+ onTokenIdentityAdmitted,
152
+ onTokenIdentityChange,
153
+ optimisticAuth,
154
+ tokenIdentityBaseline,
151
155
  children
152
156
  })
153
157
  });
@@ -156,7 +160,7 @@ function ConvexAuthProvider({ children, client, authClient, convexQueryClient, i
156
160
  * Inner provider that has access to AuthStore via useAuthStore().
157
161
  * Creates fetchAccessToken and passes it through context (no race condition).
158
162
  */
159
- function ConvexAuthProviderInner({ children, client, authClient, convexQueryClient }) {
163
+ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClient, optimisticAuth, onTokenIdentityChange, onTokenIdentityAdmitted, tokenIdentityBaseline }) {
160
164
  const authStore = useAuthStore();
161
165
  convexQueryClient?.updateAuthStore(authStore);
162
166
  const { data: session, isPending } = authClient.useSession();
@@ -239,6 +243,25 @@ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClie
239
243
  authStore,
240
244
  authClient
241
245
  ]);
246
+ const onTokenIdentityChangeRef = useRef(onTokenIdentityChange);
247
+ onTokenIdentityChangeRef.current = onTokenIdentityChange;
248
+ const onTokenIdentityAdmittedRef = useRef(onTokenIdentityAdmitted);
249
+ onTokenIdentityAdmittedRef.current = onTokenIdentityAdmitted;
250
+ const tokenIdentityBaselineRef = useRef(tokenIdentityBaseline);
251
+ tokenIdentityBaselineRef.current = tokenIdentityBaseline;
252
+ const identityGuardRef = useRef(null);
253
+ identityGuardRef.current ??= {
254
+ identity: (typeof tokenIdentityBaseline === "function" ? null : resolveTokenIdentityBaseline(tokenIdentityBaseline)) ?? decodeTokenSubjectSessionIdentity(authStore.get("token")),
255
+ currentDocumentIdentity: typeof tokenIdentityBaseline === "function" ? () => resolveTokenIdentityBaseline(tokenIdentityBaselineRef.current) : null,
256
+ tripped: false
257
+ };
258
+ const admitToken = useCallback((token, onAdmitted) => guardTokenIdentity({
259
+ client,
260
+ guard: identityGuardRef.current,
261
+ onAdmitted,
262
+ onIdentityChange: onTokenIdentityChangeRef.current,
263
+ token
264
+ }), [client]);
242
265
  const fetchAccessToken = useCallback(async ({ forceRefreshToken = false } = {}) => {
243
266
  const fetchFreshToken = () => {
244
267
  if (pendingTokenRef.current) return pendingTokenRef.current;
@@ -250,6 +273,7 @@ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClie
250
273
  }
251
274
  pendingTokenRef.current = authClient.convex.token({ fetchOptions }).then((result) => {
252
275
  const jwt = result.data?.token || null;
276
+ if (jwt && !admitToken(jwt)) return null;
253
277
  if (jwt) {
254
278
  const exp = decodeJwtExp(jwt);
255
279
  authStore.set("token", jwt);
@@ -326,8 +350,15 @@ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClie
326
350
  }, [
327
351
  authStore,
328
352
  authClient,
329
- getCachedJwt
353
+ getCachedJwt,
354
+ admitToken
330
355
  ]);
356
+ const guardedFetchAccessToken = useCallback(async (args = {}) => {
357
+ if (identityGuardRef.current?.tripped) return null;
358
+ const token = await fetchAccessToken(args);
359
+ if (!token || !admitToken(token, onTokenIdentityAdmittedRef.current)) return null;
360
+ return token;
361
+ }, [fetchAccessToken, admitToken]);
331
362
  const useAuth = useCallback(function useConvexAuthHook() {
332
363
  const token = authStore.get("token");
333
364
  const hasSession = hasActiveSessionData(sessionRef.current);
@@ -335,15 +366,18 @@ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClie
335
366
  return {
336
367
  isLoading: isPendingRef.current && !token,
337
368
  isAuthenticated: sessionMissing ? false : hasSession || token !== null,
338
- fetchAccessToken
369
+ fetchAccessToken: guardedFetchAccessToken
339
370
  };
340
- }, [fetchAccessToken, authStore]);
371
+ }, [guardedFetchAccessToken, authStore]);
341
372
  return /* @__PURE__ */ jsx(FetchAccessTokenContext.Provider, {
342
- value: fetchAccessToken,
373
+ value: guardedFetchAccessToken,
343
374
  children: /* @__PURE__ */ jsx(ConvexProviderWithAuth$1, {
344
375
  client,
345
376
  useAuth,
346
- children: /* @__PURE__ */ jsx(AuthStateSync, { children })
377
+ children: /* @__PURE__ */ jsx(AuthStateSync, {
378
+ optimisticAuth,
379
+ children
380
+ })
347
381
  })
348
382
  });
349
383
  }
@@ -359,22 +393,102 @@ function ConvexAuthProviderInner({ children, client, authClient, convexQueryClie
359
393
  * 5. Without defensive check, we'd sync { isLoading: false, isAuthenticated: false }
360
394
  * 6. Queries would throw UNAUTHORIZED before token is validated
361
395
  */
362
- function AuthStateSync({ children }) {
396
+ function AuthStateSync({ children, optimisticAuth = false }) {
363
397
  const { isLoading: convexIsLoading, isAuthenticated } = useConvexAuth();
364
398
  const authStore = useAuthStore();
365
399
  const token = useAuthValue("token");
400
+ const rejectedTokenRef = useRef(null);
366
401
  useEffect(() => {
367
- const isLoading = convexIsLoading || !!token && !isAuthenticated;
368
- authStore.set("isLoading", isLoading);
369
- authStore.set("isAuthenticated", isAuthenticated);
402
+ const gate = resolveAuthGate({
403
+ convexIsLoading,
404
+ isAuthenticated,
405
+ optimisticAuth,
406
+ rejectedToken: rejectedTokenRef.current,
407
+ token
408
+ });
409
+ if (optimisticAuth && isTokenRejectedByConvex({
410
+ convexIsLoading,
411
+ isAuthenticated,
412
+ token
413
+ })) rejectedTokenRef.current = token;
414
+ authStore.set("isLoading", gate.isLoading);
415
+ authStore.set("isAuthenticated", gate.isAuthenticated);
370
416
  }, [
371
417
  convexIsLoading,
372
418
  isAuthenticated,
419
+ optimisticAuth,
373
420
  token,
374
421
  authStore
375
422
  ]);
376
423
  return children;
377
424
  }
425
+ function decodeTokenSubjectSessionIdentity(token) {
426
+ if (!token) return null;
427
+ try {
428
+ const segment = token.split(".")[1];
429
+ if (!segment) return null;
430
+ const payload = JSON.parse(atob(segment.replaceAll("-", "+").replaceAll("_", "/")));
431
+ if (typeof payload !== "object" || payload === null) return null;
432
+ const sub = "sub" in payload ? payload.sub : null;
433
+ const sessionId = "sessionId" in payload ? payload.sessionId : null;
434
+ if (typeof sub !== "string") return null;
435
+ return `${sub}|${typeof sessionId === "string" ? sessionId : ""}`;
436
+ } catch {
437
+ return null;
438
+ }
439
+ }
440
+ function resolveTokenIdentityBaseline(baseline) {
441
+ return typeof baseline === "function" ? baseline() : baseline ?? null;
442
+ }
443
+ function guardTokenIdentity({ client, guard, onAdmitted, onIdentityChange, token }) {
444
+ if (!onIdentityChange) return true;
445
+ if (guard.tripped) return false;
446
+ const identity = decodeTokenSubjectSessionIdentity(token);
447
+ if (identity === null) return true;
448
+ const current = guard.currentDocumentIdentity?.() ?? null;
449
+ if ((guard.identity === null || guard.identity === identity) && (current === null || current === identity)) {
450
+ guard.identity = identity;
451
+ onAdmitted?.(token);
452
+ return true;
453
+ }
454
+ guard.tripped = true;
455
+ client.close();
456
+ onIdentityChange();
457
+ return false;
458
+ }
459
+ /**
460
+ * Convex has settled on "not authenticated" while a token is still held: the
461
+ * server refused it (or refused its refresh).
462
+ */
463
+ function isTokenRejectedByConvex({ convexIsLoading, isAuthenticated, token }) {
464
+ return !!token && !convexIsLoading && !isAuthenticated;
465
+ }
466
+ /**
467
+ * The auth state published to the store, which is what every query gate reads.
468
+ *
469
+ * Without `optimisticAuth` this is the confirmed state: queries wait until
470
+ * Convex has confirmed the token. With it, a held, unexpired JWT that Convex
471
+ * has not refused yet counts as authenticated while Convex confirms it, so
472
+ * auth-bound queries subscribe at once; Convex sends them after Authenticate on
473
+ * the same socket, and a refused token closes the socket before any of them is
474
+ * evaluated. When Convex refuses the token, `isAuthenticated` falls back to
475
+ * false, which resets auth-bound queries (see `CRPCProviderInner`).
476
+ */
477
+ function resolveAuthGate({ convexIsLoading, isAuthenticated, optimisticAuth, rejectedToken, token }) {
478
+ if (optimisticAuth && convexIsLoading && isOptimisticToken(token, rejectedToken)) return {
479
+ isAuthenticated: true,
480
+ isLoading: false
481
+ };
482
+ return {
483
+ isAuthenticated,
484
+ isLoading: convexIsLoading || !!token && !isAuthenticated
485
+ };
486
+ }
487
+ function isOptimisticToken(token, rejectedToken) {
488
+ if (!token || token === rejectedToken) return false;
489
+ const expiresAt = decodeJwtExp(token);
490
+ return expiresAt !== null && expiresAt > Date.now();
491
+ }
378
492
  /**
379
493
  * Handles cross-domain one-time token (OTT) verification.
380
494
  */
@@ -1599,6 +1599,7 @@ function extractRouteMap(procedures) {
1599
1599
  //#region src/server/procedure-name.ts
1600
1600
  const LOOKUP_KEY = "__KITCN_PROCEDURE_NAME_LOOKUP__";
1601
1601
  const HINTS_KEY = "__KITCN_PROCEDURE_NAME_HINTS__";
1602
+ const WARNED_MISSES_KEY = "__KITCN_PROCEDURE_NAME_WARNED_MISSES__";
1602
1603
  const PATH_SEPARATOR_RE = /\\/g;
1603
1604
  const TRIM_SLASHES_RE = /^\/+|\/+$/g;
1604
1605
  const PACKAGE_FRAME_MARKERS = ["/node_modules/kitcn/", "/packages/kitcn/"];
@@ -1632,6 +1633,14 @@ function getGlobalHints() {
1632
1633
  globalScope[HINTS_KEY] = hints;
1633
1634
  return hints;
1634
1635
  }
1636
+ function getGlobalWarnedMisses() {
1637
+ const globalScope = globalThis;
1638
+ const existing = globalScope[WARNED_MISSES_KEY];
1639
+ if (existing instanceof Set) return existing;
1640
+ const warnedMisses = /* @__PURE__ */ new Set();
1641
+ globalScope[WARNED_MISSES_KEY] = warnedMisses;
1642
+ return warnedMisses;
1643
+ }
1635
1644
  function registerProcedureNameLookup(lookup, functionsDirHint) {
1636
1645
  const globalLookup = getGlobalLookup();
1637
1646
  for (const [relativeFilePath, entries] of Object.entries(lookup)) {
@@ -1696,6 +1705,12 @@ function findBestEntry(entries, location) {
1696
1705
  return Math.abs(entry.column - location.column) < bestDistance ? entry : best;
1697
1706
  });
1698
1707
  }
1708
+ function warnAboutStaleLookup(relativeFilePath, location) {
1709
+ const warnedMisses = getGlobalWarnedMisses();
1710
+ if (warnedMisses.has(relativeFilePath)) return;
1711
+ warnedMisses.add(relativeFilePath);
1712
+ console.warn(`kitcn could not infer a procedure name at ${relativeFilePath}:${location.line} because generated/procedure-names.gen.ts has no matching entry. Run \`kitcn codegen\` to regenerate it.`);
1713
+ }
1699
1714
  function inferProcedureNameFromCallsite() {
1700
1715
  const location = captureCallsite();
1701
1716
  if (!location) return;
@@ -1703,7 +1718,12 @@ function inferProcedureNameFromCallsite() {
1703
1718
  if (!relativeFilePath) return;
1704
1719
  const entries = getGlobalLookup()[relativeFilePath];
1705
1720
  if (!entries || entries.length === 0) return;
1706
- return findBestEntry(entries, location)?.name;
1721
+ const entry = findBestEntry(entries, location);
1722
+ if (!entry) {
1723
+ warnAboutStaleLookup(relativeFilePath, location);
1724
+ return;
1725
+ }
1726
+ return entry.name;
1707
1727
  }
1708
1728
 
1709
1729
  //#endregion
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as Columns, D as TableName, E as RlsPolicies, O as createSystemFields, S as getRankIndexes, T as OrmSchemaExtensions, _ as loadEsbuild, a as generateMeta, b as getAggregateIndexes, c as resolveSchemaDefaultExport, f as createProjectJiti, g as loadDotenv, h as loadClackPrompts, i as resolveConfiguredBackend, l as schemaDeclaresAggregateIndexes, m as loadBabelParser, n as withLocalCodegenEnv, o as getConvexConfig, p as CRPC_BUILDER_STUB_SOURCE, r as loadCliConfig, s as collectSchemaTables, t as getLocalBackendEnvVars, u as logger, v as highlighter, w as EnableRLS, x as getIndexes, y as isColorEnabled } from "./local-env-CYYSNf_o.mjs";
2
+ import { C as Columns, D as TableName, E as RlsPolicies, O as createSystemFields, S as getRankIndexes, T as OrmSchemaExtensions, _ as loadEsbuild, a as generateMeta, b as getAggregateIndexes, c as resolveSchemaDefaultExport, f as createProjectJiti, g as loadDotenv, h as loadClackPrompts, i as resolveConfiguredBackend, l as schemaDeclaresAggregateIndexes, m as loadBabelParser, n as withLocalCodegenEnv, o as getConvexConfig, p as CRPC_BUILDER_STUB_SOURCE, r as loadCliConfig, s as collectSchemaTables, t as getLocalBackendEnvVars, u as logger, v as highlighter, w as EnableRLS, x as getIndexes, y as isColorEnabled } from "./local-env-DfRydipT.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import fs, { existsSync, readFileSync } from "node:fs";
5
5
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve } from "node:path";
@@ -2925,8 +2925,8 @@ function emitProcedureNameLookupLiteral(lookup) {
2925
2925
  const entries = Object.entries(lookup);
2926
2926
  if (entries.length === 0) return "{}";
2927
2927
  return `{\n${entries.map(([file, locations]) => {
2928
- const items = locations.map((location) => `{ column: ${location.column}, line: ${location.line}, name: ${JSON.stringify(location.name)} }`).join(", ");
2929
- return ` ${JSON.stringify(file)}: [${items}],`;
2928
+ const items = locations.map((location) => ` { column: ${location.column}, line: ${location.line}, name: ${JSON.stringify(location.name)} },`).join("\n");
2929
+ return ` ${JSON.stringify(file)}: [\n${items}\n ],`;
2930
2930
  }).join("\n")}\n}`;
2931
2931
  }
2932
2932
  function extractObjectLiteral(source, startIndex) {
@@ -1,4 +1,4 @@
1
- import { a as createMiddlewareFactory } from "./builder-DDYTlmTB.js";
1
+ import { a as createMiddlewareFactory } from "./builder-C4FTb9Ci.js";
2
2
 
3
3
  //#region src/plugins/middleware.ts
4
4
  const PLUGIN_CONFIG_RESOLVERS = Symbol.for("kitcn:PluginConfigResolvers");
@@ -1,5 +1,5 @@
1
1
  import { $n as TableName, $t as OrmLifecycleChange, An as ConvexCheckBuilder, At as UpdateSet, Bn as ConvexTextBuilder, Br as IsPrimaryKey, Bt as RelationsBuilderColumnBase, C as MigrationStep, Cn as ConvexVectorIndexConfig, Cr as not, Ct as OrderByClause, D as defineMigration, Dn as searchIndex, Dr as startsWith, Dt as ReturningAll, E as buildMigrationPlan, En as rankIndex, Er as or, Et as PredicateWhereIndexConfig, Fn as ConvexUniqueConstraintBuilderOn, Fr as ColumnBuilderTypeConfig, Ft as ExtractTablesFromSchema, Gn as Columns, Gt as TablesRelationalConfig, H as RlsMode, Hn as text, Hr as NotNull, In as ConvexUniqueConstraintConfig, Ir as ColumnBuilderWithTableName, It as ExtractTablesWithRelations, Jn as OrmSchemaExtensionTables, Jt as ConvexDeletionBuilder, Kt as defineRelations, Ln as check, Lr as ColumnDataType, Lt as ManyConfig, M as DatabaseWithQuery, Mn as ConvexForeignKeyBuilder, Mr as ColumnBuilder, Mt as VectorSearchProvider, N as OrmReader$1, Nn as ConvexForeignKeyConfig, Nr as ColumnBuilderBaseConfig, Nt as unsetToken, O as defineMigrationSet, On as uniqueIndex, Ot as ReturningResult, P as OrmWriter$1, Pn as ConvexUniqueConstraintBuilder, Pr as ColumnBuilderRuntimeConfig, Qn as OrmSchemaTriggers, Qt as DiscriminatorBuilderConfig, Rn as foreignKey, Rr as DrizzleEntity, Rt as OneConfig, S as MigrationStateMap, Sn as ConvexVectorIndexBuilderOn, Sr as ne, St as MutationRunMode, T as MigrationWriteMode, Tn as index, Tr as notInArray, Tt as PaginatedResult, U as EdgeMetadata, V as RlsContext, Vn as ConvexTextBuilderInitial, Vr as IsUnique, Vt as RelationsBuilderColumnConfig, W as extractRelationsConfig, Wn as Brand, Wt as TableRelationalConfig, Xn as OrmSchemaExtensions, Xt as ConvexTable, Yt as ConvexDeletionConfig, Zn as OrmSchemaRelations, Zt as ConvexTableWithColumns, _ as MigrationManifestEntry, _n as ConvexRankIndexBuilderOn, _r as isNotNull, _t as MutationExecutionMode, an as RlsPolicy, ar as UnaryExpression, at as BuildRelationResult, b as MigrationRunStatus, bn as ConvexSearchIndexConfig, br as lt, bt as MutationResult, cn as rlsPolicy, cr as contains, ct as DBQueryConfig, d as MigrationAppliedState, dn as rlsRole, dr as fieldRef, dt as InferInsertModel, en as OrmLifecycleOperation, er as BinaryExpression, f as MigrationDefinition, fn as ConvexAggregateIndexBuilder, fr as gt, ft as InferModelFromColumns, g as MigrationDriftIssue, gn as ConvexRankIndexBuilder, gr as isFieldReference, gt as MutationExecuteResult, h as MigrationDocContext, hn as ConvexIndexBuilderOn, hr as inArray, ht as MutationExecuteConfig, i as OrmMigrationCapability, in as discriminator, ir as LogicalExpression, it as BuildQueryResult, j as DatabaseWithMutations, jn as ConvexCheckConfig, jr as AnyColumn, jt as VectorQueryConfig, k as detectMigrationDrift, kn as vectorIndex, kr as SystemFields, kt as ReturningSelection, ln as RlsRole, lr as endsWith, lt as FilterOperators, m as MigrationDoc, mn as ConvexIndexBuilder, mr as ilike, mt as InsertValue, n as OrmCapabilities, nn as convexTable, nr as FieldReference, nt as AggregateFieldValue, on as RlsPolicyConfig, or as and, ot as CountConfig, p as MigrationDirection, pn as ConvexAggregateIndexBuilderOn, pr as gte, pt as InferSelectModel, qt as defineRelationsPart, r as OrmCapability, rn as deletion, rr as FilterExpression, rt as AggregateResult, sn as RlsPolicyToOption, sr as between, st as CountResult, t as OrmAggregateCapability, tn as TableConfig, tr as ExpressionVisitor, tt as AggregateConfig, un as RlsRoleConfig, ur as eq, ut as GetColumnData, v as MigrationMigrateOne, vn as ConvexSearchIndexBuilder, vr as isNull, vt as MutationPaginateConfig, w as MigrationTableName, wn as aggregateIndex, wr as notBetween, wt as OrderDirection, x as MigrationSet, xn as ConvexVectorIndexBuilder, xr as lte, xt as MutationReturning, y as MigrationPlan, yn as ConvexSearchIndexBuilderOn, yr as like, yt as MutationPaginatedResult, zn as unique, zr as HasDefault, zt as RelationsBuilder } from "../capabilities-Bxzoofl4.js";
2
- import { $ as OrmClientWithApi$1, A as ConvexDateMode, B as ConvexBytesBuilderInitial, C as ConvexNumberBuilderInitial, D as id, E as ConvexIdBuilderInitial, F as custom, G as ConvexBigIntBuilder, H as ConvexBooleanBuilder, I as json, J as CreateOrmOptions, K as ConvexBigIntBuilderInitial, L as objectOf, M as ConvexCustomBuilder, N as ConvexCustomBuilderInitial, O as ConvexDateBuilder, P as arrayOf, Q as OrmClientBase$1, R as unionOf, S as ConvexNumberBuilder, St as defineTriggers, T as ConvexIdBuilder, U as ConvexBooleanBuilderInitial, V as bytes, W as boolean, X as GenericOrmCtx$1, Y as GenericOrm$1, Z as OrmApiResult, _ as ConvexTimestampMode, _t as OrmBeforeResult, a as requireSchemaRelations, at as ScheduledMutationBatchArgs, b as ConvexTextEnumBuilderInitial, bt as OrmTriggerContext, c as TableConfigResult, ct as scheduledDeleteFactory, d as OrmNotFoundError, et as OrmFunctions, f as ConvexVectorBuilder, g as ConvexTimestampBuilderInitial, gt as defineSchemaExtension, h as ConvexTimestampBuilder, ht as SchemaExtension, i as getSchemaTriggers, it as createOrm, j as date, k as ConvexDateBuilderInitial, l as getTableColumns, m as vector, n as defineSchema, nt as OrmWriterCtx, o as asc, ot as scheduledMutationBatchFactory, p as ConvexVectorBuilderInitial, q as bigint, r as getSchemaRelations, rt as ResolveOrmSchema, s as desc, st as ScheduledDeleteArgs, t as WhereClauseResult, tt as OrmReaderCtx, u as getTableConfig, v as timestamp, vt as OrmTableTriggers, w as integer, x as textEnum, xt as OrmTriggers, y as ConvexTextEnumBuilder, yt as OrmTriggerChange, z as ConvexBytesBuilder } from "../where-clause-compiler-D2ifnjjj.js";
2
+ import { $ as OrmClientWithApi$1, A as ConvexDateMode, B as ConvexBytesBuilderInitial, C as ConvexNumberBuilderInitial, D as id, E as ConvexIdBuilderInitial, F as custom, G as ConvexBigIntBuilder, H as ConvexBooleanBuilder, I as json, J as CreateOrmOptions, K as ConvexBigIntBuilderInitial, L as objectOf, M as ConvexCustomBuilder, N as ConvexCustomBuilderInitial, O as ConvexDateBuilder, P as arrayOf, Q as OrmClientBase$1, R as unionOf, S as ConvexNumberBuilder, St as defineTriggers, T as ConvexIdBuilder, U as ConvexBooleanBuilderInitial, V as bytes, W as boolean, X as GenericOrmCtx$1, Y as GenericOrm$1, Z as OrmApiResult, _ as ConvexTimestampMode, _t as OrmBeforeResult, a as requireSchemaRelations, at as ScheduledMutationBatchArgs, b as ConvexTextEnumBuilderInitial, bt as OrmTriggerContext, c as TableConfigResult, ct as scheduledDeleteFactory, d as OrmNotFoundError, et as OrmFunctions, f as ConvexVectorBuilder, g as ConvexTimestampBuilderInitial, gt as defineSchemaExtension, h as ConvexTimestampBuilder, ht as SchemaExtension, i as getSchemaTriggers, it as createOrm, j as date, k as ConvexDateBuilderInitial, l as getTableColumns, m as vector, n as defineSchema, nt as OrmWriterCtx, o as asc, ot as scheduledMutationBatchFactory, p as ConvexVectorBuilderInitial, q as bigint, r as getSchemaRelations, rt as ResolveOrmSchema, s as desc, st as ScheduledDeleteArgs, t as WhereClauseResult, tt as OrmReaderCtx, u as getTableConfig, v as timestamp, vt as OrmTableTriggers, w as integer, x as textEnum, xt as OrmTriggers, y as ConvexTextEnumBuilder, yt as OrmTriggerChange, z as ConvexBytesBuilder } from "../where-clause-compiler-BzAJnWt4.js";
3
3
  import { i as pretendRequired, n as deprecated, r as pretend } from "../validators-0I-Ik1EN.js";
4
4
  import { a as QueryCtxWithPreferredOrmQueryTable, i as QueryCtxWithOrmQueryTable, n as LookupByIdResultByCtx, o as getByIdWithOrmQueryFallback, r as QueryCtxWithOptionalOrmQueryTable, t as DocByCtx } from "../query-context-BkNjkDCk.js";
5
5
  import { DefineSchemaOptions, GenericDatabaseReader, GenericDatabaseWriter, GenericSchema, SchemaDefinition } from "convex/server";
@@ -1,3 +1,3 @@
1
1
  import { C as MigrationStep, D as defineMigration, E as buildMigrationPlan, O as defineMigrationSet, S as MigrationStateMap, T as MigrationWriteMode, _ as MigrationManifestEntry, a as MAX_STATUS_RUN_LIMIT, b as MigrationRunStatus, c as MigrationRunChunkArgs, d as MigrationAppliedState, f as MigrationDefinition, g as MigrationDriftIssue, h as MigrationDocContext, k as detectMigrationDrift, l as MigrationStatusArgs, m as MigrationDoc, o as MigrationCancelArgs, p as MigrationDirection, s as MigrationRunArgs, u as createMigrationHandlers, v as MigrationMigrateOne, w as MigrationTableName, x as MigrationSet, y as MigrationPlan } from "../../capabilities-Bxzoofl4.js";
2
- import { Ct as migrationCapability, dt as MIGRATION_STORAGE_TABLE_NAMES, ft as injectMigrationStorageTables, lt as MIGRATION_RUN_TABLE, mt as migrationStorageTables, pt as migrationExtension, ut as MIGRATION_STATE_TABLE } from "../../where-clause-compiler-D2ifnjjj.js";
2
+ import { Ct as migrationCapability, dt as MIGRATION_STORAGE_TABLE_NAMES, ft as injectMigrationStorageTables, lt as MIGRATION_RUN_TABLE, mt as migrationStorageTables, pt as migrationExtension, ut as MIGRATION_STATE_TABLE } from "../../where-clause-compiler-BzAJnWt4.js";
3
3
  export { MAX_STATUS_RUN_LIMIT, MIGRATION_RUN_TABLE, MIGRATION_STATE_TABLE, MIGRATION_STORAGE_TABLE_NAMES, type MigrationAppliedState, type MigrationCancelArgs, type MigrationDefinition, type MigrationDirection, type MigrationDoc, type MigrationDocContext, type MigrationDriftIssue, type MigrationManifestEntry, type MigrationMigrateOne, type MigrationPlan, type MigrationRunArgs, type MigrationRunChunkArgs, type MigrationRunStatus, type MigrationSet, type MigrationStateMap, type MigrationStatusArgs, type MigrationStep, type MigrationTableName, type MigrationWriteMode, buildMigrationPlan, createMigrationHandlers, defineMigration, defineMigrationSet, detectMigrationDrift, injectMigrationStorageTables, migrationCapability, migrationExtension, migrationStorageTables };
@@ -1,3 +1,3 @@
1
- import { n as resolvePluginOptions, t as definePlugin } from "../middleware-LvZov-hA.js";
1
+ import { n as resolvePluginOptions, t as definePlugin } from "../middleware-0x76j2pR.js";
2
2
 
3
3
  export { definePlugin, resolvePluginOptions };
@@ -1,5 +1,5 @@
1
1
  import { i as decodeWire, o as encodeWire } from "./transformer-C8lVwHNT.js";
2
- import { _ as CRPCError } from "./builder-DDYTlmTB.js";
2
+ import { _ as CRPCError } from "./builder-C4FTb9Ci.js";
3
3
  import * as z$1 from "zod";
4
4
 
5
5
  //#region src/server/env.ts
@@ -1,6 +1,6 @@
1
1
  import { u as requireMutationCtx } from "../api-entry-Buvjl9hn.js";
2
- import { _ as CRPCError } from "../builder-DDYTlmTB.js";
3
- import { t as definePlugin } from "../middleware-LvZov-hA.js";
2
+ import { _ as CRPCError } from "../builder-C4FTb9Ci.js";
3
+ import { t as definePlugin } from "../middleware-0x76j2pR.js";
4
4
  import { v } from "convex/values";
5
5
  import { mutationGeneric, queryGeneric } from "convex/server";
6
6
 
@@ -5,7 +5,8 @@ import { ReactNode } from "react";
5
5
  import * as react_jsx_runtime0 from "react/jsx-runtime";
6
6
  import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
7
7
  import { DefaultError, QueryCache, QueryClient, QueryFilters, QueryFunction, QueryFunctionContext, QueryKey, SkipToken, UseMutationOptions, UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
8
- import { ConvexHttpClient } from "convex/browser";
8
+ import { Value } from "convex/values";
9
+ import { ConvexHttpClient, OptimisticUpdate } from "convex/browser";
9
10
  import { z } from "zod";
10
11
 
11
12
  //#region src/crpc/auth-error.d.ts
@@ -582,11 +583,19 @@ type DecorateInfiniteQuery<T extends FunctionReference<'query'>> = {
582
583
  infiniteQueryKey: (args?: DeepPartial<InfiniteQueryInput<FunctionArgs<T>>>) => ConvexQueryKey<T>; /** Function metadata from server (auth, limit, rateLimit, role, type) */
583
584
  meta: PaginatedFnMeta;
584
585
  };
586
+ /**
587
+ * Convex-native optimistic update for a cRPC mutation. It edits Convex's local
588
+ * query store, which the query client reads, so Convex rolls it back when the
589
+ * mutation fails. Args and query values are in their wire shape.
590
+ */
591
+ type ConvexOptimisticUpdateOption<Args extends Record<string, Value>> = {
592
+ optimisticUpdate?: OptimisticUpdate<Args>;
593
+ };
585
594
  /**
586
595
  * Decorated mutation procedure with mutationOptions and mutationKey methods.
587
596
  */
588
597
  type DecorateMutation<T extends FunctionReference<'mutation'>> = {
589
- mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$2>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
598
+ mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$2> & ConvexOptimisticUpdateOption<FunctionArgs<T>>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
590
599
  mutationKey: () => ConvexMutationKey;
591
600
  };
592
601
  /**
@@ -1087,7 +1096,7 @@ declare function useConvexActionQueryOptions<Action extends FunctionReference<'a
1087
1096
  * }));
1088
1097
  * ```
1089
1098
  */
1090
- declare function useConvexMutationOptions<Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, options?: DistributiveOmit<UseMutationOptions<FunctionReturnType<Mutation>, DefaultError, FunctionArgs<Mutation>>, ReservedMutationOptions>, transformer?: DataTransformerOptions): UseMutationOptions<FunctionReturnType<Mutation>, DefaultError, FunctionArgs<Mutation>>;
1099
+ declare function useConvexMutationOptions<Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, options?: DistributiveOmit<UseMutationOptions<FunctionReturnType<Mutation>, DefaultError, FunctionArgs<Mutation>>, ReservedMutationOptions> & ConvexOptimisticUpdateOption<FunctionArgs<Mutation>>, transformer?: DataTransformerOptions): UseMutationOptions<FunctionReturnType<Mutation>, DefaultError, FunctionArgs<Mutation>>;
1091
1100
  /**
1092
1101
  * Hook that returns action options for use with useMutation.
1093
1102
  * Wraps the Convex action with auth guard logic.
@@ -1155,10 +1155,12 @@ function useConvexMutationOptions(mutation, options, transformer) {
1155
1155
  const name = getFunctionName(mutation);
1156
1156
  const [namespace, fnName] = name.split(":");
1157
1157
  const authType = getMeta(namespace, fnName)?.auth;
1158
- const convexMutation = useMutation(mutation);
1158
+ const { optimisticUpdate, ...mutationOptions } = options ?? {};
1159
+ const reactMutation = useMutation(mutation);
1160
+ const convexMutation = optimisticUpdate ? reactMutation.withOptimisticUpdate(optimisticUpdate) : reactMutation;
1159
1161
  const resolvedTransformer = getTransformer(transformer);
1160
1162
  return {
1161
- ...options,
1163
+ ...mutationOptions,
1162
1164
  mutationFn: async (args) => {
1163
1165
  if (authType === "required" && guard()) throw new CRPCClientError({
1164
1166
  code: "UNAUTHORIZED",
@@ -1496,6 +1498,16 @@ function createCRPCContext(options) {
1496
1498
  convexSiteUrl: httpOptions.convexSiteUrl,
1497
1499
  routes: meta._http,
1498
1500
  headers: async () => {
1501
+ if (fetchAccessToken) {
1502
+ const expiresAt = authStore.get("expiresAt");
1503
+ const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
1504
+ const guardedToken = await fetchAccessToken({ forceRefreshToken: !!expiresAt && timeRemaining < 6e4 });
1505
+ const userHeaders = typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers;
1506
+ return guardedToken ? {
1507
+ ...userHeaders,
1508
+ Authorization: `Bearer ${guardedToken}`
1509
+ } : { ...userHeaders };
1510
+ }
1499
1511
  const token = authStore.get("token");
1500
1512
  const expiresAt = authStore.get("expiresAt");
1501
1513
  const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
@@ -1503,13 +1515,6 @@ function createCRPCContext(options) {
1503
1515
  ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
1504
1516
  Authorization: `Bearer ${token}`
1505
1517
  };
1506
- if (fetchAccessToken) {
1507
- const newToken = await fetchAccessToken({ forceRefreshToken: !!expiresAt });
1508
- if (newToken) return {
1509
- ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
1510
- Authorization: `Bearer ${newToken}`
1511
- };
1512
- }
1513
1518
  return { ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers };
1514
1519
  },
1515
1520
  fetch: httpOptions.fetch,
@@ -2105,7 +2110,10 @@ var ConvexQueryClient = class {
2105
2110
  this._queryClient = options.queryClient;
2106
2111
  this.unsubscribe = this.subscribeInner(options.queryClient.getQueryCache());
2107
2112
  }
2108
- if (isServer) this.serverHttpClient = new ConvexHttpClient(this.convexClient.url, { fetch: options.serverFetch });
2113
+ if (isServer) this.serverHttpClient = new ConvexHttpClient(this.convexClient.url, {
2114
+ fetch: options.serverFetch,
2115
+ logger: this.convexClient.logger
2116
+ });
2109
2117
  }
2110
2118
  /**
2111
2119
  * Connect to TanStack QueryClient.
@@ -2,7 +2,9 @@ import { n as DeepPartial, o as Simplify, r as DistributiveOmit } from "../types
2
2
  import { L as DataTransformerOptions, _ as CRPCHttpRouter, c as HttpQueryKey, k as HttpProcedure, o as HttpMutationKey, y as HttpRouterRecord } from "../http-types-BhhPZtwa.js";
3
3
  import { g as UnsetMarker } from "../types-Bkeicpo6.js";
4
4
  import { A as HttpInputArgs, C as ReservedMutationOptions$1, S as ReservedInfiniteQueryOptions, T as StaticQueryOptsParam, _ as IsPaginated, b as PaginatedFnMeta, c as ConvexMutationKey, d as ConvexQueryMeta, f as EmptyObject, g as InfiniteQueryInput, l as ConvexQueryHookOptions, m as FUNC_REF_SYMBOL, o as ConvexActionKey, p as ExtractPaginatedItem, s as ConvexInfiniteQueryMeta, u as ConvexQueryKey, w as ReservedQueryOptions$1, y as MutationVariables } from "../types-C7JkpfeY.js";
5
+ import { Value } from "convex/values";
5
6
  import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
7
+ import { OptimisticUpdate } from "convex/browser";
6
8
  import { z } from "zod";
7
9
  import { DefaultError, QueryFilters, SkipToken, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
8
10
 
@@ -70,11 +72,19 @@ type DecorateInfiniteQuery<T extends FunctionReference<'query'>> = {
70
72
  infiniteQueryKey: (args?: DeepPartial<InfiniteQueryInput<FunctionArgs<T>>>) => ConvexQueryKey<T>; /** Function metadata from server (auth, limit, rateLimit, role, type) */
71
73
  meta: PaginatedFnMeta;
72
74
  };
75
+ /**
76
+ * Convex-native optimistic update for a cRPC mutation. It edits Convex's local
77
+ * query store, which the query client reads, so Convex rolls it back when the
78
+ * mutation fails. Args and query values are in their wire shape.
79
+ */
80
+ type ConvexOptimisticUpdateOption<Args extends Record<string, Value>> = {
81
+ optimisticUpdate?: OptimisticUpdate<Args>;
82
+ };
73
83
  /**
74
84
  * Decorated mutation procedure with mutationOptions and mutationKey methods.
75
85
  */
76
86
  type DecorateMutation<T extends FunctionReference<'mutation'>> = {
77
- mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$1>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
87
+ mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$1> & ConvexOptimisticUpdateOption<FunctionArgs<T>>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
78
88
  mutationKey: () => ConvexMutationKey;
79
89
  };
80
90
  /**
@@ -1,6 +1,6 @@
1
1
  import { a as isMutationCtx, c as isSchedulerCtx, d as requireQueryCtx, f as requireRunMutationCtx, i as isActionCtx, l as requireActionCtx, n as createGeneratedFunctionReference, o as isQueryCtx, p as requireSchedulerCtx, r as getGeneratedValue, s as isRunMutationCtx, t as createApiLeaf, u as requireMutationCtx } from "../api-entry-Buvjl9hn.js";
2
2
  import { n as createLazyCaller, r as createServerCaller, t as createCallerFactory } from "../caller-factory-CEKbQdGY.js";
3
- import { A as zid, C as toCRPCError, D as zCustomAction, E as withSystemFields, M as zodOutputToConvexFields, N as zodToConvex, O as zCustomMutation, P as zodToConvexFields, S as isCRPCError, T as convexToZodFields, _ as CRPCError, a as createMiddlewareFactory, b as getCRPCErrorFromUnknown, c as registerProcedureNameLookup, d as createHttpRouterFactory, f as extractRouteMap, g as matchPathParams, h as handleHttpError, i as QueryProcedureBuilder, j as zodOutputToConvex, k as zCustomQuery, l as HttpRouterWithHono, m as extractPathParams, n as MutationProcedureBuilder, o as initCRPC, p as createHttpProcedureBuilder, r as ProcedureBuilder, s as inferProcedureNameFromCallsite, t as ActionProcedureBuilder, u as createHttpRouter, v as CRPC_ERROR_CODES_BY_KEY, w as convexToZod, x as getHTTPStatusCodeFromError, y as CRPC_ERROR_CODE_TO_HTTP } from "../builder-DDYTlmTB.js";
4
- import { a as createProcedureHandlerFactory, c as typedProcedureResolver, i as createProcedureCallerFactory, l as createEnv, n as createGenericCallerFactory, o as defineProcedure, r as createGenericHandlerFactory, s as getGeneratedFunctionReference, t as createGeneratedRegistryRuntime } from "../procedure-caller-DgGF_hA8.js";
3
+ import { A as zid, C as toCRPCError, D as zCustomAction, E as withSystemFields, M as zodOutputToConvexFields, N as zodToConvex, O as zCustomMutation, P as zodToConvexFields, S as isCRPCError, T as convexToZodFields, _ as CRPCError, a as createMiddlewareFactory, b as getCRPCErrorFromUnknown, c as registerProcedureNameLookup, d as createHttpRouterFactory, f as extractRouteMap, g as matchPathParams, h as handleHttpError, i as QueryProcedureBuilder, j as zodOutputToConvex, k as zCustomQuery, l as HttpRouterWithHono, m as extractPathParams, n as MutationProcedureBuilder, o as initCRPC, p as createHttpProcedureBuilder, r as ProcedureBuilder, s as inferProcedureNameFromCallsite, t as ActionProcedureBuilder, u as createHttpRouter, v as CRPC_ERROR_CODES_BY_KEY, w as convexToZod, x as getHTTPStatusCodeFromError, y as CRPC_ERROR_CODE_TO_HTTP } from "../builder-C4FTb9Ci.js";
4
+ import { a as createProcedureHandlerFactory, c as typedProcedureResolver, i as createProcedureCallerFactory, l as createEnv, n as createGenericCallerFactory, o as defineProcedure, r as createGenericHandlerFactory, s as getGeneratedFunctionReference, t as createGeneratedRegistryRuntime } from "../procedure-caller-CZDaDjju.js";
5
5
 
6
6
  export { ActionProcedureBuilder, CRPCError, CRPC_ERROR_CODES_BY_KEY, CRPC_ERROR_CODE_TO_HTTP, HttpRouterWithHono, MutationProcedureBuilder, ProcedureBuilder, QueryProcedureBuilder, convexToZod, convexToZodFields, createApiLeaf, createCallerFactory, createEnv, createGeneratedFunctionReference, createGeneratedRegistryRuntime, createGenericCallerFactory, createGenericHandlerFactory, createHttpProcedureBuilder, createHttpRouter, createHttpRouterFactory, createLazyCaller, createMiddlewareFactory, createProcedureCallerFactory, createProcedureHandlerFactory, createServerCaller, defineProcedure, extractPathParams, extractRouteMap, getCRPCErrorFromUnknown, getGeneratedFunctionReference, getGeneratedValue, getHTTPStatusCodeFromError, handleHttpError, inferProcedureNameFromCallsite, initCRPC, isActionCtx, isCRPCError, isMutationCtx, isQueryCtx, isRunMutationCtx, isSchedulerCtx, matchPathParams, registerProcedureNameLookup, requireActionCtx, requireMutationCtx, requireQueryCtx, requireRunMutationCtx, requireSchedulerCtx, toCRPCError, typedProcedureResolver, withSystemFields, zCustomAction, zCustomMutation, zCustomQuery, zid, zodOutputToConvex, zodOutputToConvexFields, zodToConvex, zodToConvexFields };
package/dist/watcher.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as generateMeta, d as PARSE_SNAPSHOT_SUFFIX, i as resolveConfiguredBackend, n as withLocalCodegenEnv, o as getConvexConfig, r as loadCliConfig, u as logger } from "./local-env-CYYSNf_o.mjs";
2
+ import { a as generateMeta, d as PARSE_SNAPSHOT_SUFFIX, i as resolveConfiguredBackend, n as withLocalCodegenEnv, o as getConvexConfig, r as loadCliConfig, u as logger } from "./local-env-DfRydipT.mjs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
@@ -1011,7 +1011,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1011
1011
  readonly aggregate_bucket: ConvexTableWithColumns<{
1012
1012
  name: "aggregate_bucket";
1013
1013
  columns: {
1014
- updatedAt: ConvexNumberBuilderInitial<""> & {
1014
+ count: ConvexNumberBuilderInitial<""> & {
1015
1015
  _: {
1016
1016
  notNull: true;
1017
1017
  };
@@ -1021,10 +1021,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1021
1021
  };
1022
1022
  } & {
1023
1023
  _: {
1024
- fieldName: "updatedAt";
1024
+ fieldName: "count";
1025
1025
  };
1026
1026
  };
1027
- indexName: ConvexTextBuilderInitial<""> & {
1027
+ updatedAt: ConvexNumberBuilderInitial<""> & {
1028
1028
  _: {
1029
1029
  notNull: true;
1030
1030
  };
@@ -1034,10 +1034,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1034
1034
  };
1035
1035
  } & {
1036
1036
  _: {
1037
- fieldName: "indexName";
1037
+ fieldName: "updatedAt";
1038
1038
  };
1039
1039
  };
1040
- count: ConvexNumberBuilderInitial<""> & {
1040
+ indexName: ConvexTextBuilderInitial<""> & {
1041
1041
  _: {
1042
1042
  notNull: true;
1043
1043
  };
@@ -1047,7 +1047,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1047
1047
  };
1048
1048
  } & {
1049
1049
  _: {
1050
- fieldName: "count";
1050
+ fieldName: "indexName";
1051
1051
  };
1052
1052
  };
1053
1053
  tableKey: ConvexTextBuilderInitial<""> & {
@@ -1343,7 +1343,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1343
1343
  fieldName: "value";
1344
1344
  };
1345
1345
  };
1346
- updatedAt: ConvexNumberBuilderInitial<""> & {
1346
+ count: ConvexNumberBuilderInitial<""> & {
1347
1347
  _: {
1348
1348
  notNull: true;
1349
1349
  };
@@ -1353,10 +1353,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1353
1353
  };
1354
1354
  } & {
1355
1355
  _: {
1356
- fieldName: "updatedAt";
1356
+ fieldName: "count";
1357
1357
  };
1358
1358
  };
1359
- indexName: ConvexTextBuilderInitial<""> & {
1359
+ updatedAt: ConvexNumberBuilderInitial<""> & {
1360
1360
  _: {
1361
1361
  notNull: true;
1362
1362
  };
@@ -1366,10 +1366,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1366
1366
  };
1367
1367
  } & {
1368
1368
  _: {
1369
- fieldName: "indexName";
1369
+ fieldName: "updatedAt";
1370
1370
  };
1371
1371
  };
1372
- count: ConvexNumberBuilderInitial<""> & {
1372
+ indexName: ConvexTextBuilderInitial<""> & {
1373
1373
  _: {
1374
1374
  notNull: true;
1375
1375
  };
@@ -1379,7 +1379,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1379
1379
  };
1380
1380
  } & {
1381
1381
  _: {
1382
- fieldName: "count";
1382
+ fieldName: "indexName";
1383
1383
  };
1384
1384
  };
1385
1385
  tableKey: ConvexTextBuilderInitial<""> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.33.4",
3
+ "version": "0.33.6",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",
@@ -406,11 +406,26 @@ All from `kitcn/react`:
406
406
  authClient={authClient}
407
407
  convexQueryClient={convexQueryClient} // when using TanStack Query
408
408
  initialToken={token} // from SSR (caller.getToken())
409
+ optimisticAuth // opt in: open gates while Convex confirms a held JWT
410
+ onTokenIdentityChange={() => window.location.reload()}
409
411
  onMutationUnauthorized={() => router.push('/login')}
410
412
  onQueryUnauthorized={({ queryName }) => console.log(`Unauth: ${queryName}`)}
411
413
  >
412
414
  ```
413
415
 
416
+ `optimisticAuth` only opens auth-bound query gates for a held, unexpired JWT;
417
+ expired, opaque, and refused tokens stay closed. Enable
418
+ `onTokenIdentityChange` to refuse a JWT whose `sub` or `sessionId` differs from
419
+ the document identity before Convex or HTTP sees it. The client closes before
420
+ the callback runs, so reload the document there.
421
+
422
+ For multiple provider mounts, pass `tokenIdentityBaseline` as
423
+ `sub|sessionId`, or a getter returning the document's current identity. The
424
+ getter is checked for every admission, cached tokens included.
425
+ `onTokenIdentityAdmitted(token)` observes admitted JWTs so the app can update
426
+ that shared baseline. All three identity options require
427
+ `onTokenIdentityChange`.
428
+
414
429
  For `@convex-dev/auth` (React Native):
415
430
  ```tsx
416
431
  import { ConvexProviderWithAuth } from 'kitcn/react';
@@ -298,6 +298,22 @@ const mutation = useMutation(crpc.user.update.mutationOptions({
298
298
 
299
299
  Signature: `crpc.path.mutationOptions(options?)` — standard TanStack mutation options except `mutationFn`.
300
300
 
301
+ Pass `optimisticUpdate(localStore, args)` to use Convex's optimistic local
302
+ query store. This option is removed before the TanStack options are returned;
303
+ Convex replays it as query data changes and owns rollback when the mutation
304
+ completes.
305
+
306
+ ```ts
307
+ const mutation = useMutation(crpc.todos.rename.mutationOptions({
308
+ optimisticUpdate: (store, args) => {
309
+ const todos = store.getQuery(api.todos.list, {});
310
+ if (todos) store.setQuery(api.todos.list, {}, todos.map((todo) =>
311
+ todo._id === args.id ? { ...todo, title: args.title } : todo
312
+ ));
313
+ },
314
+ }));
315
+ ```
316
+
301
317
  ### Mutation Keys
302
318
 
303
319
  ```ts