kitcn 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # kitcn
2
2
 
3
+ ## 0.32.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#425](https://github.com/udecode/kitcn/pull/425) [`495eb0b`](https://github.com/udecode/kitcn/commit/495eb0b2bd285484250ca69d75de135287531bbb) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Breaking changes
8
+
9
+ - Cursor pages for an index-union filter with no `orderBy` are now in the order of the index the read walks, grouped by the probed value, instead of creation order. Add `orderBy` to keep newest-first paging.
10
+
11
+ ```ts
12
+ // Before
13
+ const page = await db.query.users.withIndex("by_status").findMany({
14
+ where: { status: { in: ["active", "pending"] } },
15
+ cursor: null,
16
+ limit: 20,
17
+ maxScan: 500,
18
+ });
19
+
20
+ // After
21
+ const page = await db.query.users.withIndex("by_status").findMany({
22
+ where: { status: { in: ["active", "pending"] } },
23
+ orderBy: { createdAt: "desc" },
24
+ cursor: null,
25
+ limit: 20,
26
+ });
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - Page `in`, `notIn`, `ne`, and same-field equality `OR` filters from one index range per value instead of scanning the table. Cursor pagination over these filters no longer needs `maxScan`, and `orderBy` sorts across the whole result rather than per value.
32
+
33
+ ## Patches
34
+
35
+ - Fix `select()` composition and `endCursor` pagination reading a whole index instead of the compiled index ranges when the filter is an index union.
36
+ - Keep no-`orderBy` cursor direction consistent when `endCursor` routes an index union through the advanced stream path.
37
+ - Prevent `endCursor` narrowing from reopening disjoint equality ranges and duplicating merged-stream rows.
38
+ - Fix cursor pagination with a residual post-filter reading a whole index instead of the compiled index ranges when the filter is an index union.
39
+ - Fall back to a bounded scan when the probed index cannot supply the requested `orderBy` or the union is wider than 64 ranges.
40
+
41
+ ### Patch Changes
42
+
43
+ - [#426](https://github.com/udecode/kitcn/pull/426) [`466623c`](https://github.com/udecode/kitcn/commit/466623c8b581c38a066fa078309d25cfab166ea7) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
44
+
45
+ - Fix a multi-field `orderBy` reading the whole table even when a declared
46
+ compound index already produces that exact order. `orderBy: [asc(type),
47
+ asc(numLikes)]` with `limit: 5` against an index on `(type, numLikes)` now
48
+ reads 5 documents instead of every row, at any table size — previously the
49
+ read cost was the same whether you asked for 5 rows or 50. The same bound
50
+ applies to relations: `with: { posts: { orderBy: { numLikes: 'asc' },
51
+ limit: 2 } }` now reads 2 children per parent instead of all of them.
52
+ - Prefer an index that supplies more of the requested sort when several serve
53
+ the filter equally well, so `(orgId, createdAt, title)` is chosen over
54
+ `(orgId, createdAt)` for a sort on both `createdAt` and `title`. An index with
55
+ another unrequested key after `title` is not treated as an exact sort because
56
+ that key would change which tied rows survive `limit`.
57
+ - Stop warning that secondary `orderBy` fields are unstable across pages when
58
+ the index carries the whole sort. A Convex cursor is the index key, so those
59
+ pages are stable. The warning still fires — with corrected wording — when no
60
+ index serves the full sort and the extra fields really are dropped.
61
+ - Sorts that mix directions, skip an index key, or run over a column that can
62
+ be missing or null keep using the post-fetch sort, so row order and null
63
+ placement are unchanged.
64
+ - Use Convex value ordering for non-null post-fetch values, including UTF-8
65
+ strings, signed zero, and NaN, so an index-backed top-k and its post-fetch
66
+ fallback select the same rows.
67
+ - Preserve the existing implicit creation-time tie order when an
68
+ equality-pinned leading sort field points opposite to the moving fields.
69
+ Add `createdAt` in the moving direction to make that sort index-bounded.
70
+
3
71
  ## 0.31.1
4
72
 
5
73
  ### Patch Changes
@@ -1,5 +1,5 @@
1
- import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-CG-oIMyR.js";
2
- import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-Bgkzm3Y-.js";
1
+ import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-CD-Ij91k.js";
2
+ import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-FDJTqLDC.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,2 +1,2 @@
1
- import { C as GenericAuthTriggerHandlers, S as GenericAuthTriggerChange, T as defineAuth, b as GenericAuthBeforeResult, i as getGeneratedAuthDisabledReason, n as GeneratedAuthDisabledReasonKind, r as createDisabledAuthRuntime, t as AuthRuntime, w as GenericAuthTriggers, x as GenericAuthDefinition, y as BetterAuthOptionsWithoutDatabase } from "../../generated-contract-disabled-BKvk4lFx.js";
1
+ import { C as GenericAuthTriggerHandlers, S as GenericAuthTriggerChange, T as defineAuth, b as GenericAuthBeforeResult, i as getGeneratedAuthDisabledReason, n as GeneratedAuthDisabledReasonKind, r as createDisabledAuthRuntime, t as AuthRuntime, w as GenericAuthTriggers, x as GenericAuthDefinition, y as BetterAuthOptionsWithoutDatabase } from "../../generated-contract-disabled-PdNGvNYP.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-BkNjkDCk.js";
2
2
  import { t as GetAuth } from "../types-ex0-J-SC.js";
3
3
  import { t as GenericCtx } from "../context-utils-Cbv4r0AA.js";
4
- import { C as GenericAuthTriggerHandlers, S as GenericAuthTriggerChange, T as defineAuth, _ as updateManyHandler, a as AuthFunctions, b as GenericAuthBeforeResult, c as consumeOneHandler, d as createHandler, f as deleteManyHandler, g as incrementOneHandler, h as findOneHandler, i as getGeneratedAuthDisabledReason, l as countHandler, m as findManyHandler, n as GeneratedAuthDisabledReasonKind, o as Triggers, p as deleteOneHandler, r as createDisabledAuthRuntime, s as createClient, t as AuthRuntime, u as createApi, v as updateOneHandler, w as GenericAuthTriggers, x as GenericAuthDefinition, y as BetterAuthOptionsWithoutDatabase } from "../generated-contract-disabled-BKvk4lFx.js";
4
+ import { C as GenericAuthTriggerHandlers, S as GenericAuthTriggerChange, T as defineAuth, _ as updateManyHandler, a as AuthFunctions, b as GenericAuthBeforeResult, c as consumeOneHandler, d as createHandler, f as deleteManyHandler, g as incrementOneHandler, h as findOneHandler, i as getGeneratedAuthDisabledReason, l as countHandler, m as findManyHandler, n as GeneratedAuthDisabledReasonKind, o as Triggers, p as deleteOneHandler, r as createDisabledAuthRuntime, s as createClient, t as AuthRuntime, u as createApi, v as updateOneHandler, w as GenericAuthTriggers, x as GenericAuthDefinition, y as BetterAuthOptionsWithoutDatabase } from "../generated-contract-disabled-PdNGvNYP.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,29 +111,29 @@ declare const adapterWhereValidator: convex_values0.VObject<{
111
111
  mode?: "sensitive" | "insensitive" | undefined;
112
112
  connector?: "AND" | "OR" | undefined;
113
113
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
114
- value: string | number | boolean | string[] | number[] | null;
115
114
  field: string;
115
+ value: string | number | boolean | string[] | number[] | null;
116
116
  }, {
117
117
  connector: convex_values0.VUnion<"AND" | "OR" | undefined, [convex_values0.VLiteral<"AND", "required">, convex_values0.VLiteral<"OR", "required">], "optional", never>;
118
118
  field: convex_values0.VString<string, "required">;
119
119
  mode: convex_values0.VUnion<"sensitive" | "insensitive" | undefined, [convex_values0.VLiteral<"sensitive", "required">, convex_values0.VLiteral<"insensitive", "required">], "optional", never>;
120
120
  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>;
121
121
  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>;
122
- }, "required", "mode" | "value" | "connector" | "field" | "operator">;
122
+ }, "required", "mode" | "connector" | "field" | "operator" | "value">;
123
123
  declare const adapterArgsValidator: convex_values0.VObject<{
124
124
  where?: {
125
125
  mode?: "sensitive" | "insensitive" | undefined;
126
126
  connector?: "AND" | "OR" | undefined;
127
127
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
128
- value: string | number | boolean | string[] | number[] | null;
129
128
  field: string;
129
+ value: string | number | boolean | string[] | number[] | null;
130
130
  }[] | undefined;
131
131
  limit?: number | undefined;
132
132
  offset?: number | undefined;
133
133
  select?: string[] | undefined;
134
134
  sortBy?: {
135
- direction: "asc" | "desc";
136
135
  field: string;
136
+ direction: "asc" | "desc";
137
137
  } | undefined;
138
138
  model: string;
139
139
  }, {
@@ -142,32 +142,32 @@ declare const adapterArgsValidator: convex_values0.VObject<{
142
142
  offset: convex_values0.VFloat64<number | undefined, "optional">;
143
143
  select: convex_values0.VArray<string[] | undefined, convex_values0.VString<string, "required">, "optional">;
144
144
  sortBy: convex_values0.VObject<{
145
- direction: "asc" | "desc";
146
145
  field: string;
146
+ direction: "asc" | "desc";
147
147
  } | undefined, {
148
148
  direction: convex_values0.VUnion<"asc" | "desc", [convex_values0.VLiteral<"asc", "required">, convex_values0.VLiteral<"desc", "required">], "required", never>;
149
149
  field: convex_values0.VString<string, "required">;
150
- }, "optional", "direction" | "field">;
150
+ }, "optional", "field" | "direction">;
151
151
  where: convex_values0.VArray<{
152
152
  mode?: "sensitive" | "insensitive" | undefined;
153
153
  connector?: "AND" | "OR" | undefined;
154
154
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
155
- value: string | number | boolean | string[] | number[] | null;
156
155
  field: string;
156
+ value: string | number | boolean | string[] | number[] | null;
157
157
  }[] | undefined, convex_values0.VObject<{
158
158
  mode?: "sensitive" | "insensitive" | undefined;
159
159
  connector?: "AND" | "OR" | undefined;
160
160
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
161
- value: string | number | boolean | string[] | number[] | null;
162
161
  field: string;
162
+ value: string | number | boolean | string[] | number[] | null;
163
163
  }, {
164
164
  connector: convex_values0.VUnion<"AND" | "OR" | undefined, [convex_values0.VLiteral<"AND", "required">, convex_values0.VLiteral<"OR", "required">], "optional", never>;
165
165
  field: convex_values0.VString<string, "required">;
166
166
  mode: convex_values0.VUnion<"sensitive" | "insensitive" | undefined, [convex_values0.VLiteral<"sensitive", "required">, convex_values0.VLiteral<"insensitive", "required">], "optional", never>;
167
167
  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>;
168
168
  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>;
169
- }, "required", "mode" | "value" | "connector" | "field" | "operator">, "optional">;
170
- }, "required", "model" | "where" | "limit" | "offset" | "select" | "sortBy" | "sortBy.direction" | "sortBy.field">;
169
+ }, "required", "mode" | "connector" | "field" | "operator" | "value">, "optional">;
170
+ }, "required", "model" | "where" | "limit" | "offset" | "select" | "sortBy" | "sortBy.field" | "sortBy.direction">;
171
171
  declare const hasUniqueFields: (betterAuthSchema: BetterAuthDBSchema, model: string, input: Record<string, any>) => boolean;
172
172
  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>;
173
173
  declare const selectFields: <T extends TableNamesInDataModel<GenericDataModel>, D extends DocumentByName<GenericDataModel, T>>(doc: D | null, select?: string[]) => D | null;
@@ -3,8 +3,8 @@ import { r as partial } from "../validators-CIoUYCqO.js";
3
3
  import { i as defineAuth, n as createDisabledAuthRuntime, r as getGeneratedAuthDisabledReason, t as DEFAULT_AUTH_DEFINITION_PATH } from "../generated-contract-disabled-RyPYlwq0.js";
4
4
  import { n as createGeneratedFunctionReference, o as isQueryCtx, s as isRunMutationCtx } from "../api-entry-Buvjl9hn.js";
5
5
  import { i as customQuery, n as customCtx, r as customMutation } from "../customFunctions-X_B4kET8.js";
6
- import { C as eq, o as getAggregateIndexes } from "../index-utils-C1DyktHe.js";
7
- import { o as mergedStream, s as stream, t as getByIdWithOrmQueryFallback, u as unsetToken } from "../query-context-DdSg3fuk.js";
6
+ import { C as eq, o as getAggregateIndexes } from "../index-utils-DvK7P6Q1.js";
7
+ import { o as mergedStream, s as stream, t as getByIdWithOrmQueryFallback, u as unsetToken } from "../query-context-D4z1CnDH.js";
8
8
  import { n as convex } from "../convex-plugin-AbNQl1Sg.js";
9
9
  import { v } from "convex/values";
10
10
  import { internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
@@ -2967,6 +2967,17 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
2967
2967
  private _normalizeOrderByValue;
2968
2968
  private _normalizeOrderBy;
2969
2969
  private _orderBySpecs;
2970
+ /**
2971
+ * Whether rows can be missing this sort column or hold null in it.
2972
+ *
2973
+ * Only the declared validator can answer: a column that is not `.notNull()`
2974
+ * compiles to `v.optional(v.union(v.null(), T))`, so both absent and null are
2975
+ * legal stored values — and `timestamp().notNull().defaultNow()` on
2976
+ * `createdAt` is deliberately emitted as optional for migration safety, so
2977
+ * reading `config.notNull` alone would answer wrong for it. Anything the
2978
+ * lookup cannot resolve counts as nullable, which only costs a pushdown.
2979
+ */
2980
+ private _isNullableOrderField;
2970
2981
  private _resolveNonPaginatedLimit;
2971
2982
  private _compareByOrderSpecs;
2972
2983
  /**
@@ -3057,6 +3068,33 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3057
3068
  * the field produces that order.
3058
3069
  */
3059
3070
  private _findStreamOrderIndex;
3071
+ /**
3072
+ * The compiled index union, as one ordered stream.
3073
+ *
3074
+ * Each probe is its own index range, so the union is only usable where the
3075
+ * merged order is the order the read has to produce. `mergedStream` orders by
3076
+ * a *suffix* of the index key, and a suffix may only drop key components the
3077
+ * probes all pin to a single value — so the requested field has to sit inside
3078
+ * the pinned run or immediately after it. Returns null when it does not, and
3079
+ * the caller falls back to the plan's plain index range or a bounded scan.
3080
+ */
3081
+ private _buildProbeUnionStream;
3082
+ /**
3083
+ * The read the compiled plan describes, as a stream, with nothing filtered
3084
+ * yet.
3085
+ *
3086
+ * Precedence: the compiled index union, then the compiled index range, then
3087
+ * the caller's pinned `.withIndex(...)`, then an index that supplies the
3088
+ * requested order, then a full scan. The first two rungs can only ever refine
3089
+ * what the caller pinned — `_toConvexQuery` discards a compiled plan that
3090
+ * would displace a caller's index or its bounds before it gets here.
3091
+ *
3092
+ * `probeUnion` tells the caller the read is bounded by index ranges rather
3093
+ * than by scan length, which is what makes a scan budget unnecessary. A
3094
+ * rejected union stays unanchored; its original predicate remains in
3095
+ * `queryConfig.postFilters` for the caller to apply while pulling the scan.
3096
+ */
3097
+ private _buildPlanStream;
3060
3098
  private _buildBasePipelineStream;
3061
3099
  /**
3062
3100
  * Stream equivalent of the `db.query(...)` chain, used when a post-fetch
@@ -201,8 +201,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
201
201
  mode?: "sensitive" | "insensitive" | undefined;
202
202
  connector?: "AND" | "OR" | undefined;
203
203
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
204
- value: string | number | boolean | string[] | number[] | null;
205
204
  field: string;
205
+ value: string | number | boolean | string[] | number[] | null;
206
206
  }[] | undefined;
207
207
  model: string;
208
208
  }, Promise<number | null>>;
@@ -211,8 +211,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
211
211
  where?: {
212
212
  connector?: "AND" | "OR" | undefined;
213
213
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
214
- value: string | number | boolean | string[] | number[] | null;
215
214
  field: string;
215
+ value: string | number | boolean | string[] | number[] | null;
216
216
  }[] | undefined;
217
217
  model: string;
218
218
  } | {
@@ -239,8 +239,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
239
239
  where?: {
240
240
  connector?: "AND" | "OR" | undefined;
241
241
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
242
- value: string | number | boolean | string[] | number[] | null;
243
242
  field: string;
243
+ value: string | number | boolean | string[] | number[] | null;
244
244
  }[] | undefined;
245
245
  model: string;
246
246
  } | {
@@ -268,8 +268,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
268
268
  where?: {
269
269
  connector?: "AND" | "OR" | undefined;
270
270
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
271
- value: string | number | boolean | string[] | number[] | null;
272
271
  field: string;
272
+ value: string | number | boolean | string[] | number[] | null;
273
273
  }[] | undefined;
274
274
  model: string;
275
275
  } | {
@@ -283,14 +283,14 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
283
283
  mode?: "sensitive" | "insensitive" | undefined;
284
284
  connector?: "AND" | "OR" | undefined;
285
285
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
286
- value: string | number | boolean | string[] | number[] | null;
287
286
  field: string;
287
+ value: string | number | boolean | string[] | number[] | null;
288
288
  }[] | undefined;
289
289
  limit?: number | undefined;
290
290
  offset?: number | undefined;
291
291
  sortBy?: {
292
- direction: "asc" | "desc";
293
292
  field: string;
293
+ direction: "asc" | "desc";
294
294
  } | undefined;
295
295
  model: string;
296
296
  paginationOpts: {
@@ -308,8 +308,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
308
308
  mode?: "sensitive" | "insensitive" | undefined;
309
309
  connector?: "AND" | "OR" | undefined;
310
310
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
311
- value: string | number | boolean | string[] | number[] | null;
312
311
  field: string;
312
+ value: string | number | boolean | string[] | number[] | null;
313
313
  }[] | undefined;
314
314
  select?: string[] | undefined;
315
315
  model: string;
@@ -321,8 +321,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
321
321
  mode?: "sensitive" | "insensitive" | undefined;
322
322
  connector?: "AND" | "OR" | undefined;
323
323
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
324
- value: string | number | boolean | string[] | number[] | null;
325
324
  field: string;
325
+ value: string | number | boolean | string[] | number[] | null;
326
326
  }[] | undefined;
327
327
  set?: Record<string, any> | undefined;
328
328
  model: string;
@@ -335,8 +335,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
335
335
  where?: {
336
336
  connector?: "AND" | "OR" | undefined;
337
337
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
338
- value: string | number | boolean | string[] | number[] | null;
339
338
  field: string;
339
+ value: string | number | boolean | string[] | number[] | null;
340
340
  }[] | undefined;
341
341
  model: string;
342
342
  update: {
@@ -370,8 +370,8 @@ declare const createApi: <Schema extends SchemaDefinition<any, any>, DataModel e
370
370
  where?: {
371
371
  connector?: "AND" | "OR" | undefined;
372
372
  operator?: "lt" | "lte" | "gt" | "gte" | "eq" | "in" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
373
- value: string | number | boolean | string[] | number[] | null;
374
373
  field: string;
374
+ value: string | number | boolean | string[] | number[] | null;
375
375
  }[] | undefined;
376
376
  model: string;
377
377
  update: {
@@ -458,10 +458,11 @@ const hasColumnPrefix = (index, columns) => {
458
458
  *
459
459
  * Convex walks an index in full key order, so once a leading run of fields is
460
460
  * pinned to a single value by `eq`, the remainder of the scan is already sorted
461
- * by the next index field — and by `_creationTime` once every declared field is
462
- * pinned, because Convex appends it as the implicit trailing key. When that
463
- * field is the one the caller asked to sort by, `.order(dir).take(n)` returns
464
- * the exact page and nothing has to be collected and sorted afterwards.
461
+ * by the next index field, then the one after that — and by `_creationTime`
462
+ * once every declared field is consumed, because Convex appends it as the
463
+ * implicit trailing key. When that sequence is the one the caller asked to sort
464
+ * by, `.order(dir).take(n)` returns the exact page and nothing has to be
465
+ * collected and sorted afterwards.
465
466
  *
466
467
  * This is the single owner of that decision. The top-level query planner and
467
468
  * the relation loader both call it, so a bound one of them can push into the
@@ -472,12 +473,34 @@ const hasColumnPrefix = (index, columns) => {
472
473
  */
473
474
  function resolveIndexOrderPushdown(params) {
474
475
  const { indexFields, pinnedEqCount, orderSpecs } = params;
475
- if (!indexFields || orderSpecs.length !== 1) return null;
476
- const primary = orderSpecs[0];
476
+ if (!indexFields || orderSpecs.length === 0) return null;
477
477
  const eqCount = Math.min(Math.max(pinnedEqCount, 0), indexFields.length);
478
- for (let i = 0; i < eqCount; i += 1) if (indexFields[i] === primary.field) return primary.direction;
479
- const nativeField = eqCount >= indexFields.length ? INTERNAL_CREATION_TIME_FIELD : indexFields[eqCount];
480
- return primary.field === nativeField ? primary.direction : null;
478
+ const pinnedFields = new Set(indexFields.slice(0, eqCount));
479
+ /** Next unpinned index key the scan will sort by. */
480
+ let cursor = eqCount;
481
+ /** Direction every unpinned spec has to agree on. */
482
+ let direction = null;
483
+ let consumedCreationTime = false;
484
+ let hasNullableSpec = false;
485
+ for (const spec of orderSpecs) {
486
+ if (pinnedFields.has(spec.field)) continue;
487
+ if (direction === null) direction = spec.direction;
488
+ else if (direction !== spec.direction) return null;
489
+ if (spec.nullable !== false) hasNullableSpec = true;
490
+ if (consumedCreationTime) return null;
491
+ if (cursor >= indexFields.length) {
492
+ if (spec.field !== INTERNAL_CREATION_TIME_FIELD) return null;
493
+ consumedCreationTime = true;
494
+ continue;
495
+ }
496
+ if (spec.field !== indexFields[cursor]) return null;
497
+ cursor += 1;
498
+ }
499
+ if (orderSpecs.length > 1 && direction !== null && cursor < indexFields.length) return null;
500
+ const primary = orderSpecs[0];
501
+ if (direction !== null && pinnedFields.has(primary.field) && primary.direction !== direction && !consumedCreationTime) return null;
502
+ if (orderSpecs.length > 1 && hasNullableSpec) return null;
503
+ return direction ?? orderSpecs[0].direction;
481
504
  }
482
505
  function findRelationIndex(table, columns, relationName, targetTableName, strict = true, allowFullScan = false, orderSpecs = []) {
483
506
  const indexes = getIndexes(table);
@@ -1,4 +1,4 @@
1
- import { $ as RankIndexDefinition, G as CountBackfillChunkArgs, J as CountBackfillStatusArgs, K as CountBackfillKickoffArgs, Q as CountIndexDefinition, X as CountQueryPlan, Y as AggregateQueryPlan, Z as AggregateIndexDefinition, et as RankOrderField, q as CountBackfillMode, r as OrmCapability } from "../../capabilities-CG-oIMyR.js";
1
+ import { $ as RankIndexDefinition, G as CountBackfillChunkArgs, J as CountBackfillStatusArgs, K as CountBackfillKickoffArgs, Q as CountIndexDefinition, X as CountQueryPlan, Y as AggregateQueryPlan, Z as AggregateIndexDefinition, et as RankOrderField, q as CountBackfillMode, r as OrmCapability } from "../../capabilities-CD-Ij91k.js";
2
2
 
3
3
  //#region src/orm/aggregate-index/capability.d.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { t as DirectAggregate } from "../../runtime-B-8HKSIE.js";
2
2
  import { n as Columns } from "../../symbols-DDNAddkd.js";
3
- import { d as INTERNAL_CREATION_TIME_FIELD, f as PUBLIC_CREATED_AT_FIELD, m as usesSystemCreatedAtAlias } from "../../index-utils-C1DyktHe.js";
4
- import { Q as normalizeTemporalComparableValue, a as AGGREGATE_STATE_TABLE, c as getAggregateIndexDefinitions, d as COUNT_ERROR, ht as mapWithConcurrency, i as AGGREGATE_RANK_TREE_TABLE, l as getRankIndexDefinitions, m as createError, n as AGGREGATE_EXTREMA_TABLE, r as AGGREGATE_MEMBER_TABLE, s as rankAggregateName, t as AGGREGATE_BUCKET_TABLE, u as AGGREGATE_ERROR } from "../../schema-DbPcDW-N.js";
3
+ import { d as INTERNAL_CREATION_TIME_FIELD, f as PUBLIC_CREATED_AT_FIELD, m as usesSystemCreatedAtAlias } from "../../index-utils-DvK7P6Q1.js";
4
+ import { Q as normalizeTemporalComparableValue, a as AGGREGATE_STATE_TABLE, c as getAggregateIndexDefinitions, d as COUNT_ERROR, ht as mapWithConcurrency, i as AGGREGATE_RANK_TREE_TABLE, l as getRankIndexDefinitions, m as createError, n as AGGREGATE_EXTREMA_TABLE, r as AGGREGATE_MEMBER_TABLE, s as rankAggregateName, t as AGGREGATE_BUCKET_TABLE, u as AGGREGATE_ERROR } from "../../schema-Bh7AmJwY.js";
5
5
 
6
6
  //#region src/orm/transaction-cache.ts
7
7
  /**
@@ -1,5 +1,5 @@
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-CG-oIMyR.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-Bgkzm3Y-.js";
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-CD-Ij91k.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-FDJTqLDC.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";