kitcn 0.25.7 → 0.26.1

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,92 @@
1
1
  # kitcn
2
2
 
3
+ ## 0.26.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#392](https://github.com/udecode/kitcn/pull/392) [`72d3270`](https://github.com/udecode/kitcn/commit/72d327003547b1f4097aa72db0d35128ed57b04d) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
8
+
9
+ - Fix a crash when writing an object key that cannot be converted to a string,
10
+ such as one created with `Object.create(null)`, into an `aggregateIndex` or
11
+ `rankIndex`. Once enough keys accumulated to rebalance the index, the write
12
+ failed with `TypeError: Cannot convert object to primitive value` instead of
13
+ succeeding.
14
+
15
+ ## 0.26.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#401](https://github.com/udecode/kitcn/pull/401) [`947cd11`](https://github.com/udecode/kitcn/commit/947cd11fe4e461caca9d7cc934ef34d62599e136) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Breaking changes
20
+
21
+ - Require `api` on `convexBetterAuthReactStart`, which now returns `createCaller`
22
+ and `createContext` alongside the auth helpers.
23
+
24
+ ```ts
25
+ // Before
26
+ export const { handler, getToken } = convexBetterAuthReactStart({
27
+ convexUrl: import.meta.env.VITE_CONVEX_URL!,
28
+ convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
29
+ });
30
+
31
+ // After
32
+ export const { handler, getToken, createCaller, createContext } =
33
+ convexBetterAuthReactStart({
34
+ api,
35
+ convexUrl: import.meta.env.VITE_CONVEX_URL!,
36
+ convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
37
+ });
38
+ ```
39
+
40
+ - Drop `runServerCall` from the TanStack Start scaffold. Call procedures on a
41
+ caller bound once with `createCaller()`.
42
+
43
+ ```ts
44
+ // Before
45
+ export function runServerCall<T>(
46
+ fn: (caller: ServerCaller) => Promise<T> | T
47
+ ) {
48
+ const caller = createServerCaller();
49
+ return fn(caller);
50
+ }
51
+ await runServerCall((caller) => caller.user.getSessionUser({}));
52
+
53
+ // After
54
+ export const caller = createCaller();
55
+ await caller.user.getSessionUser({});
56
+ ```
57
+
58
+ - Move TanStack Start JWT caching under `auth.jwtCache`, a boolean.
59
+
60
+ ```ts
61
+ // Before
62
+ convexBetterAuthReactStart({
63
+ jwtCache: { enabled: true, isAuthError },
64
+ // ...
65
+ });
66
+
67
+ // After
68
+ convexBetterAuthReactStart({
69
+ auth: { jwtCache: true, isUnauthorized: isAuthError },
70
+ // ...
71
+ });
72
+ ```
73
+
74
+ ## Patches
75
+
76
+ - Fetch the Convex auth token once per request on TanStack Start. Every
77
+ procedure call, `getToken()`, and `fetchAuthQuery`/`fetchAuthMutation`/
78
+ `fetchAuthAction` in a request now share one token, instead of each paying its
79
+ own round trip.
80
+ - Stop replaying a rejected auth token for the rest of a request. After a
81
+ refresh, the remaining calls sharing that context use the new token instead of
82
+ re-failing and re-running non-idempotent mutations and actions.
83
+ - Refresh expired TanStack Start tokens instead of failing. Forced refresh and
84
+ token freshness now reach the token layer, so a stale token retries once and a
85
+ fresh one is no longer replayed on an authorization error.
86
+
87
+ Existing TanStack Start apps: re-run `kitcn add auth --overwrite` to update
88
+ `src/lib/convex/auth-server.ts` and `src/lib/convex/server.ts`.
89
+
3
90
  ## 0.25.7
4
91
 
5
92
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import { Bn as ConvexTextBuilderInitial, Xt as ConvexTableWithColumns } from "../capabilities-DtDfpdcH.js";
2
- import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-CwSrhq51.js";
2
+ import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-3fKontPx.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";
@@ -1,4 +1,4 @@
1
- import { n as TableAggregate$1, r as aggregateStorageTables, t as DirectAggregate$1 } from "../runtime-BdqTbgKh.js";
1
+ import { n as TableAggregate$1, r as aggregateStorageTables, t as DirectAggregate$1 } from "../runtime-CcOvOf4K.js";
2
2
 
3
3
  //#region src/aggregate/index.ts
4
4
  const wrapTriggerFactory = (methodName, factory) => ((...args) => {
@@ -1,2 +1,2 @@
1
- import { S as defineAuth, _ as GenericAuthBeforeResult, b as GenericAuthTriggerHandlers, g as BetterAuthOptionsWithoutDatabase, i as getGeneratedAuthDisabledReason, n as GeneratedAuthDisabledReasonKind, r as createDisabledAuthRuntime, t as AuthRuntime, v as GenericAuthDefinition, x as GenericAuthTriggers, y as GenericAuthTriggerChange } from "../../generated-contract-disabled-D_-_iJXw.js";
1
+ import { S as defineAuth, _ as GenericAuthBeforeResult, b as GenericAuthTriggerHandlers, g as BetterAuthOptionsWithoutDatabase, i as getGeneratedAuthDisabledReason, n as GeneratedAuthDisabledReasonKind, r as createDisabledAuthRuntime, t as AuthRuntime, v as GenericAuthDefinition, x as GenericAuthTriggers, y as GenericAuthTriggerChange } from "../../generated-contract-disabled-BEc4d98x.js";
2
2
  export { type AuthRuntime, BetterAuthOptionsWithoutDatabase, type GeneratedAuthDisabledReasonKind, GenericAuthBeforeResult, GenericAuthDefinition, GenericAuthTriggerChange, GenericAuthTriggerHandlers, GenericAuthTriggers, createDisabledAuthRuntime, defineAuth, getGeneratedAuthDisabledReason };
@@ -1,7 +1,7 @@
1
1
  import { a as QueryCtxWithPreferredOrmQueryTable, n as LookupByIdResultByCtx, t as DocByCtx } from "../query-context-DJONf8X5.js";
2
2
  import { t as GetAuth } from "../types-Bf3XQex5.js";
3
3
  import { t as GenericCtx } from "../context-utils-DwZ3Cam1.js";
4
- import { S as defineAuth, _ as GenericAuthBeforeResult, a as AuthFunctions, b as GenericAuthTriggerHandlers, c as createApi, d as deleteOneHandler, f as findManyHandler, g as BetterAuthOptionsWithoutDatabase, h as updateOneHandler, i as getGeneratedAuthDisabledReason, l as createHandler, m as updateManyHandler, n as GeneratedAuthDisabledReasonKind, o as Triggers, p as findOneHandler, r as createDisabledAuthRuntime, s as createClient, t as AuthRuntime, u as deleteManyHandler, v as GenericAuthDefinition, x as GenericAuthTriggers, y as GenericAuthTriggerChange } from "../generated-contract-disabled-D_-_iJXw.js";
4
+ import { S as defineAuth, _ as GenericAuthBeforeResult, a as AuthFunctions, b as GenericAuthTriggerHandlers, c as createApi, d as deleteOneHandler, f as findManyHandler, g as BetterAuthOptionsWithoutDatabase, h as updateOneHandler, i as getGeneratedAuthDisabledReason, l as createHandler, m as updateManyHandler, n as GeneratedAuthDisabledReasonKind, o as Triggers, p as findOneHandler, r as createDisabledAuthRuntime, s as createClient, t as AuthRuntime, u as deleteManyHandler, v as GenericAuthDefinition, x as GenericAuthTriggers, y as GenericAuthTriggerChange } from "../generated-contract-disabled-BEc4d98x.js";
5
5
  import * as convex_values0 from "convex/values";
6
6
  import { Infer } from "convex/values";
7
7
  import { AuthConfig, DocumentByName, GenericDataModel, GenericMutationCtx, GenericQueryCtx, GenericSchema, PaginationOptions, PaginationResult, SchemaDefinition, TableNamesInDataModel } from "convex/server";
@@ -111,27 +111,27 @@ type AdapterPaginationOptions = PaginationOptions & {
111
111
  };
112
112
  declare const adapterWhereValidator: convex_values0.VObject<{
113
113
  mode?: "sensitive" | "insensitive" | undefined;
114
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
115
114
  connector?: "AND" | "OR" | undefined;
115
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
116
116
  value: string | number | boolean | string[] | number[] | null;
117
117
  field: string;
118
118
  }, {
119
119
  connector: convex_values0.VUnion<"AND" | "OR" | undefined, [convex_values0.VLiteral<"AND", "required">, convex_values0.VLiteral<"OR", "required">], "optional", never>;
120
120
  field: convex_values0.VString<string, "required">;
121
121
  mode: convex_values0.VUnion<"sensitive" | "insensitive" | undefined, [convex_values0.VLiteral<"sensitive", "required">, convex_values0.VLiteral<"insensitive", "required">], "optional", never>;
122
- operator: convex_values0.VUnion<"eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined, [convex_values0.VLiteral<"lt", "required">, convex_values0.VLiteral<"lte", "required">, convex_values0.VLiteral<"gt", "required">, convex_values0.VLiteral<"gte", "required">, convex_values0.VLiteral<"eq", "required">, convex_values0.VLiteral<"in", "required">, convex_values0.VLiteral<"not_in", "required">, convex_values0.VLiteral<"ne", "required">, convex_values0.VLiteral<"contains", "required">, convex_values0.VLiteral<"starts_with", "required">, convex_values0.VLiteral<"ends_with", "required">], "optional", never>;
122
+ operator: convex_values0.VUnion<"lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined, [convex_values0.VLiteral<"lt", "required">, convex_values0.VLiteral<"lte", "required">, convex_values0.VLiteral<"gt", "required">, convex_values0.VLiteral<"gte", "required">, convex_values0.VLiteral<"eq", "required">, convex_values0.VLiteral<"in", "required">, convex_values0.VLiteral<"not_in", "required">, convex_values0.VLiteral<"ne", "required">, convex_values0.VLiteral<"contains", "required">, convex_values0.VLiteral<"starts_with", "required">, convex_values0.VLiteral<"ends_with", "required">], "optional", never>;
123
123
  value: convex_values0.VUnion<string | number | boolean | string[] | number[] | null, [convex_values0.VString<string, "required">, convex_values0.VFloat64<number, "required">, convex_values0.VBoolean<boolean, "required">, convex_values0.VArray<string[], convex_values0.VString<string, "required">, "required">, convex_values0.VArray<number[], convex_values0.VFloat64<number, "required">, "required">, convex_values0.VNull<null, "required">], "required", never>;
124
- }, "required", "mode" | "operator" | "value" | "field" | "connector">;
124
+ }, "required", "mode" | "value" | "connector" | "field" | "operator">;
125
125
  declare const adapterArgsValidator: convex_values0.VObject<{
126
- limit?: number | undefined;
126
+ select?: string[] | undefined;
127
127
  where?: {
128
128
  mode?: "sensitive" | "insensitive" | undefined;
129
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
130
129
  connector?: "AND" | "OR" | undefined;
130
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
131
131
  value: string | number | boolean | string[] | number[] | null;
132
132
  field: string;
133
133
  }[] | undefined;
134
- select?: string[] | undefined;
134
+ limit?: number | undefined;
135
135
  offset?: number | undefined;
136
136
  sortBy?: {
137
137
  field: string;
@@ -152,24 +152,24 @@ declare const adapterArgsValidator: convex_values0.VObject<{
152
152
  }, "optional", "field" | "direction">;
153
153
  where: convex_values0.VArray<{
154
154
  mode?: "sensitive" | "insensitive" | undefined;
155
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
156
155
  connector?: "AND" | "OR" | undefined;
156
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
157
157
  value: string | number | boolean | string[] | number[] | null;
158
158
  field: string;
159
159
  }[] | undefined, convex_values0.VObject<{
160
160
  mode?: "sensitive" | "insensitive" | undefined;
161
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
162
161
  connector?: "AND" | "OR" | undefined;
162
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
163
163
  value: string | number | boolean | string[] | number[] | null;
164
164
  field: string;
165
165
  }, {
166
166
  connector: convex_values0.VUnion<"AND" | "OR" | undefined, [convex_values0.VLiteral<"AND", "required">, convex_values0.VLiteral<"OR", "required">], "optional", never>;
167
167
  field: convex_values0.VString<string, "required">;
168
168
  mode: convex_values0.VUnion<"sensitive" | "insensitive" | undefined, [convex_values0.VLiteral<"sensitive", "required">, convex_values0.VLiteral<"insensitive", "required">], "optional", never>;
169
- operator: convex_values0.VUnion<"eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined, [convex_values0.VLiteral<"lt", "required">, convex_values0.VLiteral<"lte", "required">, convex_values0.VLiteral<"gt", "required">, convex_values0.VLiteral<"gte", "required">, convex_values0.VLiteral<"eq", "required">, convex_values0.VLiteral<"in", "required">, convex_values0.VLiteral<"not_in", "required">, convex_values0.VLiteral<"ne", "required">, convex_values0.VLiteral<"contains", "required">, convex_values0.VLiteral<"starts_with", "required">, convex_values0.VLiteral<"ends_with", "required">], "optional", never>;
169
+ operator: convex_values0.VUnion<"lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined, [convex_values0.VLiteral<"lt", "required">, convex_values0.VLiteral<"lte", "required">, convex_values0.VLiteral<"gt", "required">, convex_values0.VLiteral<"gte", "required">, convex_values0.VLiteral<"eq", "required">, convex_values0.VLiteral<"in", "required">, convex_values0.VLiteral<"not_in", "required">, convex_values0.VLiteral<"ne", "required">, convex_values0.VLiteral<"contains", "required">, convex_values0.VLiteral<"starts_with", "required">, convex_values0.VLiteral<"ends_with", "required">], "optional", never>;
170
170
  value: convex_values0.VUnion<string | number | boolean | string[] | number[] | null, [convex_values0.VString<string, "required">, convex_values0.VFloat64<number, "required">, convex_values0.VBoolean<boolean, "required">, convex_values0.VArray<string[], convex_values0.VString<string, "required">, "required">, convex_values0.VArray<number[], convex_values0.VFloat64<number, "required">, "required">, convex_values0.VNull<null, "required">], "required", never>;
171
- }, "required", "mode" | "operator" | "value" | "field" | "connector">, "optional">;
172
- }, "required", "limit" | "where" | "model" | "select" | "offset" | "sortBy" | "sortBy.field" | "sortBy.direction">;
171
+ }, "required", "mode" | "value" | "connector" | "field" | "operator">, "optional">;
172
+ }, "required", "model" | "select" | "where" | "limit" | "offset" | "sortBy" | "sortBy.field" | "sortBy.direction">;
173
173
  declare const hasUniqueFields: (betterAuthSchema: BetterAuthDBSchema, model: string, input: Record<string, any>) => boolean;
174
174
  declare const checkUniqueFields: <Schema extends SchemaDefinition<any, any>>(ctx: GenericQueryCtx<GenericDataModel>, schema: Schema, betterAuthSchema: BetterAuthDBSchema, table: string, input: Record<string, any>, doc?: Record<string, any>) => Promise<void>;
175
175
  declare const selectFields: <T extends TableNamesInDataModel<GenericDataModel>, D extends DocumentByName<GenericDataModel, T>>(doc: D | null, select?: string[]) => D | null;
@@ -1,6 +1,6 @@
1
1
  import { t as getToken } from "../../token-DcV_0fkF.js";
2
2
  import { n as defaultIsUnauthorized } from "../../error-CMLeCadS.js";
3
- import { t as createCallerFactory } from "../../caller-factory-Dd3H7j3V.js";
3
+ import { t as createCallerFactory } from "../../caller-factory-D4pz5GcZ.js";
4
4
 
5
5
  //#region src/auth-nextjs/index.ts
6
6
  /** biome-ignore-all lint/suspicious/noExplicitAny: lib */
@@ -1,12 +1,66 @@
1
+ import { H as ConvexContext, W as LazyCaller } from "../../../procedure-name-l2YusEZI.js";
1
2
  import { t as GetTokenOptions } from "../../../token-kQaqFby4.js";
2
3
  import { FunctionReference, FunctionReturnType, OptionalRestArgs } from "convex/server";
3
4
 
4
5
  //#region src/auth-start/server.d.ts
5
- type ConvexBetterAuthReactStartOptions = Omit<GetTokenOptions, 'forceRefresh'> & {
6
+ /** Auth options for server-side calls. */
7
+ type AuthOptions = {
8
+ /** Better Auth auth route base path. Defaults to `/api/auth`. */basePath?: string;
9
+ /**
10
+ * Read the Convex JWT from the session cookie instead of fetching it.
11
+ * Default: false.
12
+ *
13
+ * The token this saves a round trip on is also the token
14
+ * `syncConvexAuthForStartLoader` hands the browser Convex client, which
15
+ * captures it for the lifetime of the socket. A cookie JWT can be within
16
+ * seconds of expiry and cannot be renewed from that callback, so enable this
17
+ * only for apps that do not prime the browser client from `getToken()`.
18
+ */
19
+ jwtCache?: boolean; /** Custom function to detect UNAUTHORIZED errors. Default checks code property. */
20
+ isUnauthorized?: (error: unknown) => boolean; /** Expiration tolerance in seconds. */
21
+ expirationToleranceSeconds?: number;
22
+ };
23
+ type ConvexBetterAuthReactStartOptions<TApi> = Omit<GetTokenOptions, 'forceRefresh' | 'jwtCache'> & {
24
+ /** Your Convex API object. */api: TApi;
6
25
  convexSiteUrl: string;
7
- convexUrl: string;
26
+ convexUrl: string; /** Auth options. */
27
+ auth?: AuthOptions;
8
28
  };
9
- declare const convexBetterAuthReactStart: (opts: ConvexBetterAuthReactStartOptions) => {
29
+ /**
30
+ * Create Convex caller factory with Better Auth integration for TanStack Start.
31
+ *
32
+ * Every request resolves its Convex token once. TanStack Start runs each request
33
+ * inside an `AsyncLocalStorage` scope, so `getRequest()` returns an object whose
34
+ * identity is stable for that request and distinct across requests. Keying the
35
+ * memos on it makes them request-scoped by construction: nothing auth-related is
36
+ * ever held at module scope, and entries are collected with the request.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * // auth-server.ts
41
+ * export const { createCaller, handler, getToken } = convexBetterAuthReactStart({
42
+ * api,
43
+ * convexUrl: import.meta.env.VITE_CONVEX_URL!,
44
+ * convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
45
+ * });
46
+ *
47
+ * // server.ts
48
+ * export const caller = createCaller();
49
+ *
50
+ * // any server function or server route - single token fetch per request
51
+ * const user = await caller.user.getSessionUser();
52
+ * const posts = await caller.posts.list();
53
+ * ```
54
+ */
55
+ declare const convexBetterAuthReactStart: <TApi extends Record<string, unknown>>(opts: ConvexBetterAuthReactStartOptions<TApi>) => {
56
+ /**
57
+ * Bind a cRPC caller. Defaults to the request-scoped context, so every
58
+ * procedure call in a request shares one token fetch.
59
+ */
60
+ createCaller: (ctxFn?: () => Promise<ConvexContext<TApi>>) => LazyCaller<TApi>;
61
+ createContext: (reqOpts: {
62
+ headers: Headers;
63
+ }) => Promise<ConvexContext<TApi>>;
10
64
  getToken: () => Promise<string | undefined>;
11
65
  handler: (request: Request) => Promise<Response>;
12
66
  fetchAuthQuery: <Query extends FunctionReference<"query">>(query: Query, ...args: OptionalRestArgs<Query>) => Promise<FunctionReturnType<Query>>;
@@ -1,12 +1,11 @@
1
1
  import { t as getToken } from "../../../token-DcV_0fkF.js";
2
+ import { n as defaultIsUnauthorized } from "../../../error-CMLeCadS.js";
3
+ import { t as createCallerFactory } from "../../../caller-factory-D4pz5GcZ.js";
2
4
  import { stripIndent } from "common-tags";
3
- import { getRequestHeaders } from "@tanstack/react-start/server";
5
+ import { getRequest } from "@tanstack/react-start/server";
4
6
  import { ConvexHttpClient } from "convex/browser";
5
- import React from "react";
6
7
 
7
8
  //#region src/auth-start/server.ts
8
- const fallbackCache = (fn) => fn;
9
- const cache = React.cache ?? fallbackCache;
10
9
  const TRAILING_COLON_RE = /:$/;
11
10
  const requestCanHaveBody = (method) => method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
12
11
  const stripHopByHopHeaders = (headers) => {
@@ -79,30 +78,134 @@ const handler = async (request, opts) => {
79
78
  redirect: "manual"
80
79
  });
81
80
  };
81
+ /**
82
+ * Create Convex caller factory with Better Auth integration for TanStack Start.
83
+ *
84
+ * Every request resolves its Convex token once. TanStack Start runs each request
85
+ * inside an `AsyncLocalStorage` scope, so `getRequest()` returns an object whose
86
+ * identity is stable for that request and distinct across requests. Keying the
87
+ * memos on it makes them request-scoped by construction: nothing auth-related is
88
+ * ever held at module scope, and entries are collected with the request.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // auth-server.ts
93
+ * export const { createCaller, handler, getToken } = convexBetterAuthReactStart({
94
+ * api,
95
+ * convexUrl: import.meta.env.VITE_CONVEX_URL!,
96
+ * convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
97
+ * });
98
+ *
99
+ * // server.ts
100
+ * export const caller = createCaller();
101
+ *
102
+ * // any server function or server route - single token fetch per request
103
+ * const user = await caller.user.getSessionUser();
104
+ * const posts = await caller.posts.list();
105
+ * ```
106
+ */
82
107
  const convexBetterAuthReactStart = (opts) => {
83
108
  const siteUrl = parseConvexSiteUrl(opts.convexSiteUrl);
84
- const cachedGetToken = cache(async (opts) => {
85
- const headers = getRequestHeaders();
109
+ const auth = opts.auth ?? {};
110
+ const jwtCacheEnabled = auth.jwtCache === true;
111
+ const fetchTokenFor = (headers, forceRefresh) => {
86
112
  const mutableHeaders = new Headers(headers);
87
113
  stripHopByHopHeaders(mutableHeaders);
88
114
  mutableHeaders.set("accept-encoding", "identity");
89
- return getToken(siteUrl, mutableHeaders, opts);
115
+ return getToken(siteUrl, mutableHeaders, {
116
+ basePath: auth.basePath ?? opts.basePath,
117
+ cookiePrefix: opts.cookiePrefix,
118
+ forceRefresh,
119
+ jwtCache: {
120
+ enabled: jwtCacheEnabled,
121
+ expirationToleranceSeconds: auth.expirationToleranceSeconds,
122
+ isAuthError: auth.isUnauthorized ?? defaultIsUnauthorized
123
+ }
124
+ });
125
+ };
126
+ const headersByRequest = /* @__PURE__ */ new WeakMap();
127
+ const tokenByRequest = /* @__PURE__ */ new WeakMap();
128
+ const contextByRequest = /* @__PURE__ */ new WeakMap();
129
+ /**
130
+ * Snapshot the current request's headers once.
131
+ *
132
+ * `getRequestHeaders()` is not identity-stable within a request: srvx serves a
133
+ * lazy header view until anything materializes the native Request (a server
134
+ * function reading `formData()`, for example), then swaps in that Request's
135
+ * own `Headers` and drops the old one. Header *values* survive the swap, so a
136
+ * snapshot taken at any point is correct, but comparing or keying on the live
137
+ * accessor would silently stop matching mid-request.
138
+ */
139
+ const requestHeaders = (request) => {
140
+ const cached = headersByRequest.get(request);
141
+ if (cached) return cached;
142
+ const snapshot = new Headers(request.headers);
143
+ headersByRequest.set(request, snapshot);
144
+ return snapshot;
145
+ };
146
+ /**
147
+ * Memoize `create` for the lifetime of the current request. The pending
148
+ * promise is stored before it settles so concurrent callers share one
149
+ * in-flight fetch, and a rejection is evicted so the next caller can retry.
150
+ */
151
+ const perRequest = (store, create) => {
152
+ const request = getRequest();
153
+ const cached = store.get(request);
154
+ if (cached) return cached;
155
+ const pending = create(request).catch((error) => {
156
+ if (store.get(request) === pending) store.delete(request);
157
+ throw error;
158
+ });
159
+ store.set(request, pending);
160
+ return pending;
161
+ };
162
+ /**
163
+ * The current request, or `undefined` outside a Start request scope.
164
+ * `getRequest()` throws there, and an explicit `createContext({ headers })`
165
+ * is allowed to run with no ambient request at all.
166
+ */
167
+ const currentRequest = () => {
168
+ try {
169
+ return getRequest();
170
+ } catch {
171
+ return;
172
+ }
173
+ };
174
+ /** Ambient token for the current request, resolved at most once. */
175
+ const requestToken = () => perRequest(tokenByRequest, (request) => fetchTokenFor(requestHeaders(request)));
176
+ const { createContext, createCaller } = createCallerFactory({
177
+ api: opts.api,
178
+ auth: {
179
+ getToken: (_tokenSiteUrl, headers, getTokenOpts) => {
180
+ const forceRefresh = getTokenOpts?.forceRefresh;
181
+ const request = currentRequest();
182
+ if (!forceRefresh && request && headers === headersByRequest.get(request)) return requestToken();
183
+ return fetchTokenFor(headers, forceRefresh);
184
+ },
185
+ isUnauthorized: auth.isUnauthorized
186
+ },
187
+ convexSiteUrl: opts.convexSiteUrl,
188
+ convexUrl: opts.convexUrl
90
189
  });
190
+ /** Request-scoped context. One context, one token, per request. */
191
+ const createRequestContext = () => perRequest(contextByRequest, (request) => createContext({ headers: requestHeaders(request) }));
91
192
  const callWithToken = async (fn) => {
92
- const token = await cachedGetToken(opts) ?? {};
193
+ const token = await requestToken();
93
194
  try {
94
- return await fn(token?.token);
195
+ return await fn(token.token);
95
196
  } catch (error) {
96
- if (!opts?.jwtCache?.enabled || token.isFresh || !opts.jwtCache?.isAuthError(error)) throw error;
97
- return await fn((await cachedGetToken({
98
- ...opts,
99
- forceRefresh: true
100
- })).token);
197
+ if (token.isFresh || !(auth.isUnauthorized ?? defaultIsUnauthorized)(error)) throw error;
198
+ const refreshed = await fetchTokenFor(requestHeaders(getRequest()), true);
199
+ token.token = refreshed.token;
200
+ token.isFresh = refreshed.isFresh;
201
+ return await fn(refreshed.token);
101
202
  }
102
203
  };
103
204
  return {
205
+ createCaller: (ctxFn = createRequestContext) => createCaller(ctxFn),
206
+ createContext,
104
207
  getToken: async () => {
105
- return (await cachedGetToken(opts)).token;
208
+ return (await requestToken()).token;
106
209
  },
107
210
  handler: async (request) => cloneAuthHandlerResponse(await handler(request, opts)),
108
211
  fetchAuthQuery: async (query, ...args) => {
@@ -164,6 +164,8 @@ function createCallerFactory(opts) {
164
164
  ...opts,
165
165
  forceRefresh: true
166
166
  });
167
+ tokenResult.token = newToken.token;
168
+ tokenResult.isFresh = newToken.isFresh;
167
169
  try {
168
170
  return await fn(newToken.token);
169
171
  } catch (retryError) {
package/dist/cli.mjs CHANGED
@@ -5636,15 +5636,19 @@ export const Route = createFileRoute('/api/auth/$')({
5636
5636
 
5637
5637
  //#endregion
5638
5638
  //#region src/cli/registry/items/auth/auth-start-server.template.ts
5639
- const AUTH_START_SERVER_TEMPLATE = `import { convexBetterAuthReactStart } from 'kitcn/auth/start/server';
5639
+ const AUTH_START_SERVER_TEMPLATE = `import { api } from '@convex/api';
5640
+ import { convexBetterAuthReactStart } from 'kitcn/auth/start/server';
5640
5641
 
5641
5642
  export const {
5642
5643
  handler,
5643
5644
  getToken,
5645
+ createCaller,
5646
+ createContext,
5644
5647
  fetchAuthQuery,
5645
5648
  fetchAuthMutation,
5646
5649
  fetchAuthAction,
5647
5650
  } = convexBetterAuthReactStart({
5651
+ api,
5648
5652
  convexUrl: import.meta.env.VITE_CONVEX_URL!,
5649
5653
  convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
5650
5654
  });
@@ -5652,41 +5656,9 @@ export const {
5652
5656
 
5653
5657
  //#endregion
5654
5658
  //#region src/cli/registry/items/auth/auth-start-server-call.template.ts
5655
- const AUTH_START_SERVER_CALL_TEMPLATE = `import { api } from '@convex/api';
5656
- import { getRequestHeaders } from '@tanstack/react-start/server';
5657
- import { createCallerFactory } from 'kitcn/server';
5658
-
5659
- import { getToken } from '@/lib/convex/auth-server';
5660
-
5661
- const { createContext, createCaller } = createCallerFactory({
5662
- api,
5663
- convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
5664
- auth: {
5665
- getToken: async () => {
5666
- return {
5667
- token: await getToken(),
5668
- };
5669
- },
5670
- },
5671
- });
5672
-
5673
- type ServerCaller = ReturnType<typeof createCaller>;
5674
-
5675
- async function makeContext() {
5676
- const headers = await getRequestHeaders();
5677
- return createContext({ headers });
5678
- }
5659
+ const AUTH_START_SERVER_CALL_TEMPLATE = `import { createCaller } from '@/lib/convex/auth-server';
5679
5660
 
5680
- function createServerCaller(): ServerCaller {
5681
- return createCaller(async () => {
5682
- return await makeContext();
5683
- });
5684
- }
5685
-
5686
- export function runServerCall<T>(fn: (caller: ServerCaller) => Promise<T> | T) {
5687
- const caller = createServerCaller();
5688
- return fn(caller);
5689
- }
5661
+ export const caller = createCaller();
5690
5662
  `;
5691
5663
 
5692
5664
  //#endregion
@@ -181,18 +181,10 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
181
181
  };
182
182
  }, Promise<any>>;
183
183
  deleteMany: convex_server0.RegisteredMutation<"internal", {
184
- paginationOpts: {
185
- id?: number;
186
- endCursor?: string | null;
187
- maximumRowsRead?: number;
188
- maximumBytesRead?: number;
189
- numItems: number;
190
- cursor: string | null;
191
- };
192
184
  input: {
193
185
  where?: {
194
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
195
186
  connector?: "AND" | "OR" | undefined;
187
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
196
188
  value: string | number | boolean | string[] | number[] | null;
197
189
  field: string;
198
190
  }[] | undefined;
@@ -201,6 +193,14 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
201
193
  where?: any[] | undefined;
202
194
  model: string;
203
195
  };
196
+ paginationOpts: {
197
+ id?: number;
198
+ endCursor?: string | null;
199
+ maximumRowsRead?: number;
200
+ maximumBytesRead?: number;
201
+ numItems: number;
202
+ cursor: string | null;
203
+ };
204
204
  }, Promise<{
205
205
  count: number;
206
206
  ids: any[];
@@ -212,8 +212,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
212
212
  deleteOne: convex_server0.RegisteredMutation<"internal", {
213
213
  input: {
214
214
  where?: {
215
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
216
215
  connector?: "AND" | "OR" | undefined;
216
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
217
217
  value: string | number | boolean | string[] | number[] | null;
218
218
  field: string;
219
219
  }[] | undefined;
@@ -224,20 +224,21 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
224
224
  };
225
225
  }, Promise<Record<string, unknown> | undefined>>;
226
226
  findMany: convex_server0.RegisteredQuery<"internal", {
227
- limit?: number | undefined;
228
227
  join?: any;
229
228
  where?: {
230
229
  mode?: "sensitive" | "insensitive" | undefined;
231
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
232
230
  connector?: "AND" | "OR" | undefined;
231
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
233
232
  value: string | number | boolean | string[] | number[] | null;
234
233
  field: string;
235
234
  }[] | undefined;
235
+ limit?: number | undefined;
236
236
  offset?: number | undefined;
237
237
  sortBy?: {
238
238
  field: string;
239
239
  direction: "asc" | "desc";
240
240
  } | undefined;
241
+ model: string;
241
242
  paginationOpts: {
242
243
  id?: number;
243
244
  endCursor?: string | null;
@@ -246,48 +247,47 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
246
247
  numItems: number;
247
248
  cursor: string | null;
248
249
  };
249
- model: string;
250
250
  }, Promise<PaginationResult<convex_server0.GenericDocument>>>;
251
251
  findOne: convex_server0.RegisteredQuery<"internal", {
252
252
  join?: any;
253
+ select?: string[] | undefined;
253
254
  where?: {
254
255
  mode?: "sensitive" | "insensitive" | undefined;
255
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
256
256
  connector?: "AND" | "OR" | undefined;
257
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
257
258
  value: string | number | boolean | string[] | number[] | null;
258
259
  field: string;
259
260
  }[] | undefined;
260
- select?: string[] | undefined;
261
261
  model: string;
262
262
  }, Promise<convex_server0.GenericDocument | null>>;
263
263
  getLatestJwks: convex_server0.RegisteredAction<"internal", {}, Promise<unknown>>;
264
264
  rotateKeys: convex_server0.RegisteredAction<"internal", {}, Promise<unknown>>;
265
265
  updateMany: convex_server0.RegisteredMutation<"internal", {
266
- paginationOpts: {
267
- id?: number;
268
- endCursor?: string | null;
269
- maximumRowsRead?: number;
270
- maximumBytesRead?: number;
271
- numItems: number;
272
- cursor: string | null;
273
- };
274
266
  input: {
275
267
  where?: {
276
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
277
268
  connector?: "AND" | "OR" | undefined;
269
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
278
270
  value: string | number | boolean | string[] | number[] | null;
279
271
  field: string;
280
272
  }[] | undefined;
273
+ model: string;
281
274
  update: {
282
275
  [x: string]: unknown;
283
276
  [x: number]: unknown;
284
277
  [x: symbol]: unknown;
285
278
  };
286
- model: string;
287
279
  } | {
288
280
  where?: any[] | undefined;
289
- update: any;
290
281
  model: string;
282
+ update: any;
283
+ };
284
+ paginationOpts: {
285
+ id?: number;
286
+ endCursor?: string | null;
287
+ maximumRowsRead?: number;
288
+ maximumBytesRead?: number;
289
+ numItems: number;
290
+ cursor: string | null;
291
291
  };
292
292
  }, Promise<{
293
293
  count: number;
@@ -300,21 +300,21 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
300
300
  updateOne: convex_server0.RegisteredMutation<"internal", {
301
301
  input: {
302
302
  where?: {
303
- operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | undefined;
304
303
  connector?: "AND" | "OR" | undefined;
304
+ operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
305
305
  value: string | number | boolean | string[] | number[] | null;
306
306
  field: string;
307
307
  }[] | undefined;
308
+ model: string;
308
309
  update: {
309
310
  [x: string]: unknown;
310
311
  [x: number]: unknown;
311
312
  [x: symbol]: unknown;
312
313
  };
313
- model: string;
314
314
  } | {
315
315
  where?: any[] | undefined;
316
- update: any;
317
316
  model: string;
317
+ update: any;
318
318
  };
319
319
  }, Promise<any>>;
320
320
  };
@@ -1,4 +1,4 @@
1
- import { t as DirectAggregate } from "../../runtime-BdqTbgKh.js";
1
+ import { t as DirectAggregate } from "../../runtime-CcOvOf4K.js";
2
2
  import { a as Columns } from "../../table-CX2lnX7e.js";
3
3
  import { Y as normalizeTemporalComparableValue, bt as PUBLIC_CREATED_AT_FIELD, c as AGGREGATE_ERROR, f as createError, i as AGGREGATE_STATE_TABLE, l as COUNT_ERROR, lt as mapWithConcurrency, n as AGGREGATE_EXTREMA_TABLE, o as getAggregateIndexDefinitions, r as AGGREGATE_MEMBER_TABLE, s as getRankIndexDefinitions, t as AGGREGATE_BUCKET_TABLE, xt as usesSystemCreatedAtAlias, yt as INTERNAL_CREATION_TIME_FIELD } from "../../schema-e0wbZ_Ax.js";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import { $n as BinaryExpression, $t as OrmLifecycleOperation, A as DatabaseWithMutations, An as ConvexCheckConfig, Ar as AnyColumn, At as VectorQueryConfig, B as RlsContext, Bn as ConvexTextBuilderInitial, Br as IsUnique, Bt as RelationsBuilderColumnConfig, C as MigrationTableName, Cn as aggregateIndex, Cr as notBetween, Ct as OrderDirection, D as defineMigrationSet, Dn as uniqueIndex, Dt as ReturningResult, E as defineMigration, En as searchIndex, Er as startsWith, Et as ReturningAll, Fn as ConvexUniqueConstraintConfig, Fr as ColumnBuilderWithTableName, Ft as ExtractTablesWithRelations, Gt as defineRelations, H as EdgeMetadata, In as check, Ir as ColumnDataType, It as ManyConfig, Jt as ConvexDeletionConfig, Kt as defineRelationsPart, Ln as foreignKey, Lr as DrizzleEntity, Lt as OneConfig, M as OrmReader$1, Mn as ConvexForeignKeyConfig, Mr as ColumnBuilderBaseConfig, Mt as unsetToken, N as OrmWriter$1, Nn as ConvexUniqueConstraintBuilder, Nr as ColumnBuilderRuntimeConfig, O as detectMigrationDrift, On as vectorIndex, Or as SystemFields, Ot as ReturningSelection, Pn as ConvexUniqueConstraintBuilderOn, Pr as ColumnBuilderTypeConfig, Pt as ExtractTablesFromSchema, Qn as TableName, Qt as OrmLifecycleChange, Rn as unique, Rr as HasDefault, Rt as RelationsBuilder, S as MigrationStep, Sn as ConvexVectorIndexConfig, Sr as not, St as OrderByClause, T as buildMigrationPlan, Tn as rankIndex, Tr as or, Tt as PredicateWhereIndexConfig, U as extractRelationsConfig, Un as Brand, Ut as TableRelationalConfig, V as RlsMode, Vn as text, Vr as NotNull, Wn as Columns, Wt as TablesRelationalConfig, Xn as OrmSchemaRelations, Xt as ConvexTableWithColumns, Yn as OrmSchemaExtensions, Yt as ConvexTable, Zn as OrmSchemaTriggers, Zt as DiscriminatorBuilderConfig, _ as MigrationMigrateOne, _n as ConvexSearchIndexBuilder, _r as isNull, _t as MutationPaginateConfig, an as RlsPolicyConfig, ar as and, at as CountConfig, b as MigrationSet, bn as ConvexVectorIndexBuilder, br as lte, bt as MutationReturning, cn as RlsRole, cr as endsWith, ct as FilterOperators, d as MigrationDefinition, dn as ConvexAggregateIndexBuilder, dr as gt, dt as InferModelFromColumns, en as TableConfig, er as ExpressionVisitor, et as AggregateConfig, f as MigrationDirection, fn as ConvexAggregateIndexBuilderOn, fr as gte, ft as InferSelectModel, g as MigrationManifestEntry, gn as ConvexRankIndexBuilderOn, gr as isNotNull, gt as MutationExecutionMode, h as MigrationDriftIssue, hn as ConvexRankIndexBuilder, hr as isFieldReference, ht as MutationExecuteResult, i as OrmMigrationCapability, in as RlsPolicy, ir as UnaryExpression, it as BuildRelationResult, j as DatabaseWithQuery, jn as ConvexForeignKeyBuilder, jr as ColumnBuilder, jt as VectorSearchProvider, kn as ConvexCheckBuilder, kt as UpdateSet, ln as RlsRoleConfig, lr as eq, lt as GetColumnData, m as MigrationDocContext, mn as ConvexIndexBuilderOn, mr as inArray, mt as MutationExecuteConfig, n as OrmCapabilities, nn as deletion, nr as FilterExpression, nt as AggregateResult, on as RlsPolicyToOption, or as between, ot as CountResult, p as MigrationDoc, pn as ConvexIndexBuilder, pr as ilike, pt as InsertValue, qn as OrmSchemaExtensionTables, qt as ConvexDeletionBuilder, r as OrmCapability, rn as discriminator, rr as LogicalExpression, rt as BuildQueryResult, sn as rlsPolicy, sr as contains, st as DBQueryConfig, t as OrmAggregateCapability, tn as convexTable, tr as FieldReference, tt as AggregateFieldValue, u as MigrationAppliedState, un as rlsRole, ur as fieldRef, ut as InferInsertModel, v as MigrationPlan, vn as ConvexSearchIndexBuilderOn, vr as like, vt as MutationPaginatedResult, w as MigrationWriteMode, wn as index, wr as notInArray, wt as PaginatedResult, x as MigrationStateMap, xn as ConvexVectorIndexBuilderOn, xr as ne, xt as MutationRunMode, y as MigrationRunStatus, yn as ConvexSearchIndexConfig, yr as lt, yt as MutationResult, zn as ConvexTextBuilder, zr as IsPrimaryKey, zt as RelationsBuilderColumnBase } from "../capabilities-DtDfpdcH.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-CwSrhq51.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-3fKontPx.js";
3
3
  import { i as pretendRequired, n as deprecated, r as pretend } from "../validators-wOIjhkfN.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-DJONf8X5.js";
5
5
  import { DefineSchemaOptions, GenericDatabaseReader, GenericDatabaseWriter, GenericSchema, SchemaDefinition } from "convex/server";
@@ -1,3 +1,3 @@
1
1
  import { C as MigrationTableName, D as defineMigrationSet, E as defineMigration, O as detectMigrationDrift, S as MigrationStep, T as buildMigrationPlan, _ as MigrationMigrateOne, a as MigrationCancelArgs, b as MigrationSet, c as MigrationStatusArgs, d as MigrationDefinition, f as MigrationDirection, g as MigrationManifestEntry, h as MigrationDriftIssue, l as createMigrationHandlers, m as MigrationDocContext, o as MigrationRunArgs, p as MigrationDoc, s as MigrationRunChunkArgs, u as MigrationAppliedState, v as MigrationPlan, w as MigrationWriteMode, x as MigrationStateMap, y as MigrationRunStatus } from "../../capabilities-DtDfpdcH.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-CwSrhq51.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-3fKontPx.js";
3
3
  export { 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 };
@@ -439,7 +439,7 @@ async function insertIntoNode(ctx, maxNodeSize, node, item) {
439
439
  const minNodeSize = minNodeSizeFor(maxNodeSize);
440
440
  if (newN.items.length > maxNodeSize) {
441
441
  if (newN.items.length !== maxNodeSize + 1 || newN.items.length !== 2 * minNodeSize + 1) throw new Error(`bad ${newN.items.length}`);
442
- log(`splitting node ${newN._id} at ${newN.items[minNodeSize].k}`);
442
+ log(`splitting node ${newN._id} at ${p(newN.items[minNodeSize].k)}`);
443
443
  const topLevel = nodeCounts(newN);
444
444
  const subCounts = await subtreeCounts(ctx.db, newN);
445
445
  const leftCount = add(accumulate(topLevel.slice(0, minNodeSize)), accumulate(subCounts.length ? subCounts.slice(0, minNodeSize + 1) : []));
@@ -1,5 +1,5 @@
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-CkDpGYVg.js";
2
- import { n as createLazyCaller, r as createServerCaller, t as createCallerFactory } from "../caller-factory-Dd3H7j3V.js";
2
+ import { n as createLazyCaller, r as createServerCaller, t as createCallerFactory } from "../caller-factory-D4pz5GcZ.js";
3
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-CsAw-DK8.js";
4
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-BR-Wb0si.js";
5
5
 
@@ -102,22 +102,22 @@ declare const migrationStorageTables: {
102
102
  fieldName: "status";
103
103
  };
104
104
  };
105
- direction: ConvexTextBuilderInitial<""> & {
105
+ cursor: ConvexTextBuilderInitial<""> & {
106
106
  _: {
107
107
  tableName: "migration_state";
108
108
  };
109
109
  } & {
110
110
  _: {
111
- fieldName: "direction";
111
+ fieldName: "cursor";
112
112
  };
113
113
  };
114
- cursor: ConvexTextBuilderInitial<""> & {
114
+ direction: ConvexTextBuilderInitial<""> & {
115
115
  _: {
116
116
  tableName: "migration_state";
117
117
  };
118
118
  } & {
119
119
  _: {
120
- fieldName: "cursor";
120
+ fieldName: "direction";
121
121
  };
122
122
  };
123
123
  updatedAt: ConvexNumberBuilderInitial<""> & {
@@ -1323,7 +1323,11 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1323
1323
  readonly aggregate_extrema: ConvexTableWithColumns<{
1324
1324
  name: "aggregate_extrema";
1325
1325
  columns: {
1326
- count: ConvexNumberBuilderInitial<""> & {
1326
+ value: ConvexCustomBuilderInitial<"", convex_values0.VAny<any, "required", string>> & {
1327
+ _: {
1328
+ $type: convex_values0.Value;
1329
+ };
1330
+ } & {
1327
1331
  _: {
1328
1332
  notNull: true;
1329
1333
  };
@@ -1333,14 +1337,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1333
1337
  };
1334
1338
  } & {
1335
1339
  _: {
1336
- fieldName: "count";
1340
+ fieldName: "value";
1337
1341
  };
1338
1342
  };
1339
- value: ConvexCustomBuilderInitial<"", convex_values0.VAny<any, "required", string>> & {
1340
- _: {
1341
- $type: convex_values0.Value;
1342
- };
1343
- } & {
1343
+ count: ConvexNumberBuilderInitial<""> & {
1344
1344
  _: {
1345
1345
  notNull: true;
1346
1346
  };
@@ -1350,7 +1350,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1350
1350
  };
1351
1351
  } & {
1352
1352
  _: {
1353
- fieldName: "value";
1353
+ fieldName: "count";
1354
1354
  };
1355
1355
  };
1356
1356
  updatedAt: ConvexNumberBuilderInitial<""> & {
@@ -1744,22 +1744,22 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1744
1744
  fieldName: "status";
1745
1745
  };
1746
1746
  };
1747
- direction: ConvexTextBuilderInitial<""> & {
1747
+ cursor: ConvexTextBuilderInitial<""> & {
1748
1748
  _: {
1749
1749
  tableName: "migration_state";
1750
1750
  };
1751
1751
  } & {
1752
1752
  _: {
1753
- fieldName: "direction";
1753
+ fieldName: "cursor";
1754
1754
  };
1755
1755
  };
1756
- cursor: ConvexTextBuilderInitial<""> & {
1756
+ direction: ConvexTextBuilderInitial<""> & {
1757
1757
  _: {
1758
1758
  tableName: "migration_state";
1759
1759
  };
1760
1760
  } & {
1761
1761
  _: {
1762
- fieldName: "cursor";
1762
+ fieldName: "direction";
1763
1763
  };
1764
1764
  };
1765
1765
  updatedAt: ConvexNumberBuilderInitial<""> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.25.7",
3
+ "version": "0.26.1",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",
@@ -77,6 +77,7 @@ export const authClient = createAuthClient({
77
77
  ```ts
78
78
  // convex/functions/schema.ts
79
79
  import {
80
+ aggregateIndex,
80
81
  convexTable,
81
82
  defineSchema,
82
83
  id,
@@ -111,6 +112,8 @@ export const member = convexTable(
111
112
  index("userId").on(t.userId),
112
113
  index("organizationId_userId").on(t.organizationId, t.userId),
113
114
  index("organizationId_role").on(t.organizationId, t.role),
115
+ // Backs `member.count({ where: { organizationId } })` for seat limits.
116
+ aggregateIndex("by_organization").on(t.organizationId),
114
117
  ]
115
118
  );
116
119
 
@@ -134,6 +137,10 @@ export const invitation = convexTable(
134
137
  t.status
135
138
  ),
136
139
  index("organizationId_status").on(t.organizationId, t.status),
140
+ // Backs `invitation.count({ where: { organizationId, status } })`.
141
+ // `count()` index matching is exact-set, so the aggregate key must list
142
+ // every field the filter constrains.
143
+ aggregateIndex("by_organization_status").on(t.organizationId, t.status),
137
144
  ]
138
145
  );
139
146
 
@@ -145,6 +152,11 @@ export const session = convexTable("session", {
145
152
  });
146
153
  ```
147
154
 
155
+ `count()` requires its aggregate index to be `READY`. Backfill these indexes
156
+ before routing live traffic to the count path. When schema and count code ship
157
+ together, catch only `COUNT_INDEX_BUILDING` and read the exact count from the
158
+ matching native organization index until backfill completes.
159
+
148
160
  ### Teams (Optional)
149
161
 
150
162
  ```ts
@@ -506,16 +518,18 @@ export const inviteMember = authMutation
506
518
  permissions: { invitation: ["create"] },
507
519
  });
508
520
 
509
- // Check member limit
510
- const members = await ctx.orm.query.member.findMany({
511
- where: { organizationId: input.organizationId },
512
- limit: DEFAULT_LIST_LIMIT,
513
- });
514
- const pending = await ctx.orm.query.invitation.findMany({
515
- where: { organizationId: input.organizationId, status: "pending" },
516
- limit: DEFAULT_LIST_LIMIT,
517
- });
518
- if (members.length + pending.length >= MEMBER_LIMIT) {
521
+ // Check member limit. Count off `aggregateIndex`, never by collecting rows
522
+ // to read `.length` -- that puts every member and pending invitation into
523
+ // the transaction's read set to produce two integers.
524
+ const [members, pending] = await Promise.all([
525
+ ctx.orm.query.member.count({
526
+ where: { organizationId: input.organizationId },
527
+ }),
528
+ ctx.orm.query.invitation.count({
529
+ where: { organizationId: input.organizationId, status: "pending" },
530
+ }),
531
+ ]);
532
+ if (members + pending >= MEMBER_LIMIT) {
519
533
  throw new CRPCError({
520
534
  code: "FORBIDDEN",
521
535
  message: `Organization member limit reached. Maximum ${MEMBER_LIMIT} members allowed.`,
@@ -34,20 +34,28 @@ export const {
34
34
  **Create:** `src/lib/convex/auth-server.ts`
35
35
 
36
36
  ```ts
37
+ import { api } from "@convex/api";
37
38
  import { convexBetterAuthReactStart } from "kitcn/auth/start/server";
38
39
 
39
40
  export const {
40
41
  handler,
41
42
  getToken,
43
+ createCaller,
44
+ createContext,
42
45
  fetchAuthQuery,
43
46
  fetchAuthMutation,
44
47
  fetchAuthAction,
45
48
  } = convexBetterAuthReactStart({
49
+ api,
46
50
  convexUrl: import.meta.env.VITE_CONVEX_URL!,
47
51
  convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
48
52
  });
49
53
  ```
50
54
 
55
+ `getToken` fetches the Convex token from the auth route. `auth.jwtCache: true`
56
+ reads it from the session cookie instead. Leave it off when the browser Convex
57
+ client is primed from `getToken()`.
58
+
51
59
  For client-side route loaders that fetch protected Convex queries through the
52
60
  router `queryClient`, prime the shared Convex client in the root `beforeLoad`
53
61
  before child loaders run:
@@ -78,7 +86,7 @@ export const Route = createRootRouteWithContext<{
78
86
  });
79
87
  ```
80
88
 
81
- Use `runServerCall` or `fetchAuthQuery` for server-side loaders. Use
89
+ Use the request-scoped `caller` or `fetchAuthQuery` for server-side loaders. Use
82
90
  `syncConvexAuthForStartLoader` only for client/router loaders that execute
83
91
  shared `ConvexQueryClient` queries before `ConvexAuthProvider` mounts.
84
92
 
@@ -105,42 +113,20 @@ export const Route = createFileRoute("/api/auth/$" as never)({
105
113
  **Create:** `src/lib/convex/server.ts`
106
114
 
107
115
  ```ts
108
- import { api } from "@convex/api";
109
- import { getRequestHeaders } from "@tanstack/react-start/server";
110
- import { createCallerFactory } from "kitcn/server";
116
+ import { createCaller } from "@/lib/convex/auth-server";
111
117
 
112
- import { getToken } from "@/lib/convex/auth-server";
113
-
114
- const { createContext, createCaller } = createCallerFactory({
115
- api,
116
- convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
117
- auth: {
118
- getToken: async () => {
119
- return {
120
- token: await getToken(),
121
- };
122
- },
123
- },
124
- });
125
-
126
- type ServerCaller = ReturnType<typeof createCaller>;
127
-
128
- async function makeContext() {
129
- const headers = await getRequestHeaders();
130
- return createContext({ headers });
131
- }
118
+ export const caller = createCaller();
119
+ ```
132
120
 
133
- function createServerCaller(): ServerCaller {
134
- return createCaller(async () => {
135
- return await makeContext();
136
- });
137
- }
121
+ `createCaller()` binds to the current request, so every procedure call in a
122
+ request shares one Convex auth token fetch. The module-scope `caller` holds no
123
+ request state; it resolves the current request on each call. Pass a context
124
+ factory (`createCaller(() => createContext({ headers }))`) only to call Convex
125
+ with headers other than the current request's.
138
126
 
139
- export function runServerCall<T>(fn: (caller: ServerCaller) => Promise<T> | T) {
140
- const caller = createServerCaller();
141
- return fn(caller);
142
- }
143
- ```
127
+ Reach `caller` from a `createServerFn` handler or a server route. A route
128
+ `loader` also runs in the browser on client-side navigation, where there is no
129
+ request scope.
144
130
 
145
131
  Use the docs pattern from `tanstack-start.mdx` for:
146
132