kitcn 0.32.2 → 0.33.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,107 @@
1
1
  # kitcn
2
2
 
3
+ ## 0.33.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#456](https://github.com/udecode/kitcn/pull/456) [`5b0bd5a`](https://github.com/udecode/kitcn/commit/5b0bd5a1debb4a0e0bb87f79ef1a18f357b614c1) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Breaking changes
8
+
9
+ - Support index-ordered pagination for indexed filters with more than 64 values. Pages follow index order, grouped by the filtered value, rather than creation order. Add `orderBy` and `maxScan` to preserve newest-first paging.
10
+
11
+ ```ts
12
+ // Before
13
+ const page = await db.query.users.withIndex("by_status").findMany({
14
+ where: { status: { in: manyStatuses } },
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: manyStatuses } },
23
+ orderBy: { createdAt: "desc" },
24
+ cursor: null,
25
+ limit: 20,
26
+ maxScan: 500,
27
+ });
28
+ ```
29
+
30
+ ## Patches
31
+
32
+ - Fix authenticated cRPC query results disappearing when a server-rendered page
33
+ hydrates.
34
+ - Fix unnecessary full-table reads for `select()` filters containing more than 64 values.
35
+ - Fix unnecessary full-table reads for long `in` lists combined with another condition, such as `name: { contains: 'x' }`.
36
+ - Improve limited reads with additional conditions so they stop after enough matching rows are found when index order satisfies the requested sort.
37
+ - Support index-bounded reads for indexed `in`, `notIn`, `ne`, and same-field equality `OR` filters regardless of list length.
38
+ - Support pagination without `maxScan` for wide filters whose requested order follows their indexed values; cross-value sorting, such as `orderBy: { createdAt: 'desc' }`, still requires `maxScan` past 64 values.
39
+
40
+ - [#449](https://github.com/udecode/kitcn/pull/449) [`b8df2da`](https://github.com/udecode/kitcn/commit/b8df2da49a8733b545f02b84bb538b1cbef275b0) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Breaking changes
41
+
42
+ - Read a `select().flatMap(relation, { where })` stage through an index that extends the relation's foreign key when the schema declares one. Children arrive in that index's order, so a lowered range field now orders them ahead of creation time, and outstanding page cursors for those queries do not carry over.
43
+
44
+ ```ts
45
+ // Before: every post by the author is read, then filtered, in creation order.
46
+ // After: only the by_author_likes range is read, in numLikes order.
47
+ index("by_author_likes").on(t.authorId, t.numLikes);
48
+
49
+ await ctx.orm.query.users
50
+ .select()
51
+ .flatMap("posts", { includeParent: false, where: { numLikes: { gt: 10 } } })
52
+ .paginate({ cursor: null, limit: 20 });
53
+ ```
54
+
55
+ ## Patches
56
+
57
+ - Compile a `select().union([{ where }])` source `where` against the table's indexes instead of filtering every scanned row, so an object `where` on an indexed field bounds the read. A source keeps its unanchored read when the lowered one could not supply `interleaveBy`, and a `where` never displaces an index the source or the chain pinned with a range.
58
+ - Report what a caller can actually do when a `predicate(...)` `where` runs over an unbounded pipeline read: a union source names its own `index` option, and a `flatMap` stage names the relation index it needs.
59
+ - Resolve a `select()` union source or `flatMap` stage `where` once per read. A callback `where` runs a single time instead of twice, and an object `where` compiles once instead of once per row.
60
+
61
+ ### Patch Changes
62
+
63
+ - [#455](https://github.com/udecode/kitcn/pull/455) [`a73de28`](https://github.com/udecode/kitcn/commit/a73de28bacf3e518a38762442270d2c54f6989e8) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
64
+
65
+ - Fix `findMany` losing its read bound when a `limit` is combined with `in`, `ne`,
66
+ `notIn` or a same-field `OR` on a table that has RLS enabled, or alongside a
67
+ filter Convex cannot evaluate such as `contains`. Either one used to make the
68
+ query read every row it matched against, so
69
+ `findMany({ where: { ownerId: { in: [a, b] } }, limit: 3 })` read 500 documents
70
+ on a 500-row table and 200 on a 200-row table. It now reads 6 at either size,
71
+ and the count no longer grows with the table.
72
+ - Improve how that `limit` is counted, so it bounds rows the caller can actually
73
+ see: with 80 rows an RLS policy hides sitting in front of the matches,
74
+ `limit: 3` still returns three rows and reads 86 documents instead of 200.
75
+ - Support that bound for an `in` list of any length.
76
+
77
+ Rows and their order are unchanged. A `where` that filters through a relation
78
+ keeps its previous read cost, as does `ne`, `notIn` or `isNotNull` ordered by a
79
+ field no index can serve.
80
+
81
+ - [#450](https://github.com/udecode/kitcn/pull/450) [`781af65`](https://github.com/udecode/kitcn/commit/781af65c6e2f9128362378f0cf358d2c8e509b7d) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
82
+
83
+ - Improve update read costs on `aggregateIndex` and `rankIndex` tables: read
84
+ each row once unless a user `update.before` hook requires a fresh read.
85
+ - Fix CLEARING checks for deletes of already-deleted rows: report the
86
+ transient aggregate-index state instead of `Delete on non-existent doc`.
87
+
88
+ - [#451](https://github.com/udecode/kitcn/pull/451) [`80f7609`](https://github.com/udecode/kitcn/commit/80f7609cb107d23e4688b6877f35787107dd0a39) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
89
+
90
+ - Improve `aggregateIndex` bulk-write read costs by reusing bucket and member
91
+ reads within uninterrupted ORM statements. User hooks and policy callbacks
92
+ end reuse so nested mutation writes remain visible to later maintenance.
93
+
94
+ - [#454](https://github.com/udecode/kitcn/pull/454) [`f399843`](https://github.com/udecode/kitcn/commit/f399843c6bfb46e080558bc22d2cc4a77842cd16) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
95
+
96
+ - Improve bulk aggregate writes by folding shared bucket and extrema updates
97
+ within uninterrupted statements. A 40-row key migration writes two shared
98
+ buckets and two extrema entries, plus forty membership rows.
99
+ - Preserve read-your-own-writes through aggregate reads and nested functions
100
+ called by lifecycle hooks or RLS policies. Reads and callbacks flush pending
101
+ writes; callbacks suspend batching until they settle.
102
+ - Fix mid-statement aggregate reads through `withoutTriggers()` and an ORM
103
+ rebuilt from a hook context to observe the same rows as `ctx.orm`.
104
+
3
105
  ## 0.32.2
4
106
 
5
107
  ### Patch Changes
@@ -1,5 +1,5 @@
1
- import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-BJm_VSDT.js";
2
- import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-Dgf4lrO-.js";
1
+ import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-Bxzoofl4.js";
2
+ import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-DrV6lQg0.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-BRm1dNQE.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-BRm1dNQE.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,22 +111,22 @@ 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;
@@ -152,21 +152,21 @@ declare const adapterArgsValidator: convex_values0.VObject<{
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">;
169
+ }, "required", "mode" | "connector" | "field" | "operator" | "value">, "optional">;
170
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>;
@@ -4,7 +4,7 @@ import { i as defineAuth, n as createDisabledAuthRuntime, r as getGeneratedAuthD
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
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";
7
+ import { c as stream, f as unsetToken, s as mergedStream, t as getByIdWithOrmQueryFallback } from "../query-context-DQLv58LH.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";
@@ -2149,7 +2149,7 @@ declare const createAggregateError: (code: AggregateErrorCode, message: string)
2149
2149
  declare const ensureCountAllowedForRls: (tableConfig: TableRelationalConfig, rlsMode: "skip" | "default" | undefined) => void;
2150
2150
  declare const ensureAggregateAllowedForRls: (tableConfig: TableRelationalConfig, rlsMode: "skip" | "default" | undefined, methodName: string) => void;
2151
2151
  declare namespace runtime_d_exports$1 {
2152
- export { AGGREGATE_ERROR, AGGREGATE_STATE_KIND_METRIC, AGGREGATE_STATE_KIND_RANK, AggregateBucketDelta, AggregateErrorCode, AggregateExtremaDelta, AggregateIndexDefinition, AggregateMemberWrite, AggregateMembershipDelta, AggregateQueryPlan, AggregateRangeConstraint, COUNT_ERROR, COUNT_STATUS_BUILDING, COUNT_STATUS_CLEARING, COUNT_STATUS_READY, ClearIndexChunkResult, CountErrorCode, CountIndexDefinition, CountQueryPlan, CountState, NULLISH_PROBE_VALUES, PlanBucketReadCache, applyAggregateIndexesForChange, applyCountIndexesForChange, asUnknownArray, assertAggregateIndexesWritable, clearCountIndexChunk, compileAggregateQueryPlan, compileCountFieldQueryPlan, compileCountQueryPlan, computeAggregateMembershipDelta, computeAggregateMetricValues, computeCountKeyParts, createAggregateError, createCountError, ensureAggregateAllowedForRls, ensureAggregateIndexReady, ensureCountAllowedForRls, ensureCountIndexReady, flushAggregateMembershipDeltas, getAggregateIndexDefinitions, getCountIndexDefinitions, getCountIndexValuesForFields, getCountState, isAggregatePlanZero, isIndexCountZero, listCountStates, listSchemaAggregateIndexes, listSchemaCountIndexes, parseAggregateWhere, parseCountWhere, readAverageFromBuckets, readCountFieldFromBuckets, readCountFromBuckets, readExtremaFromBuckets, readPlanBuckets, readSumFromBuckets, reconcileAggregateMembership, serializeCountKeyParts, setCountState, setCountStateError };
2152
+ export { AGGREGATE_ERROR, AGGREGATE_STATE_KIND_METRIC, AGGREGATE_STATE_KIND_RANK, AggregateBucketDelta, AggregateErrorCode, AggregateExtremaDelta, AggregateIndexDefinition, AggregateMemberWrite, AggregateMembershipDelta, AggregateQueryPlan, AggregateRangeConstraint, COUNT_ERROR, COUNT_STATUS_BUILDING, COUNT_STATUS_CLEARING, COUNT_STATUS_READY, ClearIndexChunkResult, CountErrorCode, CountIndexDefinition, CountMemberRow, CountQueryPlan, CountState, NULLISH_PROBE_VALUES, PlanBucketReadCache, applyAggregateIndexesForChange, applyCountIndexesForChange, asUnknownArray, assertAggregateIndexesWritable, clearCountIndexChunk, compileAggregateQueryPlan, compileCountFieldQueryPlan, compileCountQueryPlan, computeAggregateMembershipDelta, computeAggregateMetricValues, computeCountKeyParts, createAggregateError, createCountError, ensureAggregateAllowedForRls, ensureAggregateIndexReady, ensureCountAllowedForRls, ensureCountIndexReady, flushAggregateMembershipDeltas, getAggregateIndexDefinitions, getCountIndexDefinitions, getCountIndexValuesForFields, getCountState, isAggregatePlanZero, isIndexCountZero, listCountStates, listSchemaAggregateIndexes, listSchemaCountIndexes, parseAggregateWhere, parseCountWhere, readAverageFromBuckets, readCountFieldFromBuckets, readCountFromBuckets, readExtremaFromBuckets, readPlanBuckets, readSumFromBuckets, reconcileAggregateMembership, serializeCountKeyParts, setCountState, setCountStateError };
2153
2153
  }
2154
2154
  declare const AGGREGATE_STATE_KIND_METRIC = "metric";
2155
2155
  declare const AGGREGATE_STATE_KIND_RANK = "rank";
@@ -2188,6 +2188,21 @@ type CountState = {
2188
2188
  completedAt?: number | null;
2189
2189
  lastError?: string | null;
2190
2190
  };
2191
+ type CountMemberRow = {
2192
+ _id: GenericId<any>;
2193
+ kind: string;
2194
+ tableKey: string;
2195
+ indexName: string;
2196
+ docId: string;
2197
+ keyHash: string;
2198
+ keyParts: unknown[];
2199
+ sumValues: Record<string, number>;
2200
+ nonNullCountValues: Record<string, number>;
2201
+ extremaValues: Record<string, unknown>;
2202
+ rankNamespace?: unknown;
2203
+ rankKey?: unknown;
2204
+ rankSumValue?: number;
2205
+ };
2191
2206
  type CountBucketRow = {
2192
2207
  _id: GenericId<any>;
2193
2208
  tableKey: string;
@@ -2261,6 +2276,11 @@ type AggregateExtremaDelta = {
2261
2276
  value: unknown;
2262
2277
  delta: number;
2263
2278
  };
2279
+ /**
2280
+ * `post` is what the write leaves in the member table, so the flush can bring
2281
+ * the member memo forward without reading the row back. The `_id` is missing
2282
+ * because an insert only learns it once the insert returns.
2283
+ */
2264
2284
  type AggregateMemberWrite = {
2265
2285
  kind: 'none';
2266
2286
  } | {
@@ -2270,11 +2290,13 @@ type AggregateMemberWrite = {
2270
2290
  kind: 'patch';
2271
2291
  id: GenericId<any>;
2272
2292
  doc: Record<string, unknown>;
2293
+ post: Omit<CountMemberRow, '_id'>;
2273
2294
  } | {
2274
2295
  kind: 'insert';
2275
- doc: Record<string, unknown>;
2296
+ doc: Omit<CountMemberRow, '_id'>;
2276
2297
  };
2277
2298
  type AggregateMembershipDelta = {
2299
+ /** The document this delta reconciles, and the member memo's key. */docId: string;
2278
2300
  buckets: AggregateBucketDelta[];
2279
2301
  extrema: AggregateExtremaDelta[];
2280
2302
  member: AggregateMemberWrite;
@@ -2293,8 +2315,28 @@ declare const computeAggregateMembershipDelta: (db: GenericDatabaseWriter<any>,
2293
2315
  */
2294
2316
  declare const flushAggregateMembershipDeltas: (db: GenericDatabaseWriter<any>, tableName: string, indexName: string, deltas: AggregateMembershipDelta[]) => Promise<void>;
2295
2317
  /**
2296
- * Single-document reconciliation. Flushes eagerly so user code reading an
2297
- * aggregate later in the same mutation sees its own writes.
2318
+ * Single-document reconciliation.
2319
+ *
2320
+ * The bucket and extrema writes always go on the transaction's write queue,
2321
+ * never straight to storage: `applyBucketDelta` writes an absolute count
2322
+ * computed from the row it just read, so two of them interleaving would be a
2323
+ * lost update, and routing every one of them through a single drain is what
2324
+ * keeps them serialized. Inside a mutation statement the queue is held to the
2325
+ * end of the statement, which is what lets the fold collapse a page of
2326
+ * documents into one write per key tuple; outside one it is drained
2327
+ * immediately, so a raw `ctx.db` write behaves exactly as it did before.
2328
+ *
2329
+ * The member row is written per document either way, because it is the
2330
+ * pre-image the next reconciliation of this document subtracts. Outside a
2331
+ * statement it is still written after the bucket, preserving the previous
2332
+ * order; inside one it necessarily lands first, and a flush that throws
2333
+ * part-way leaves the index needing a backfill — which a partially applied fold
2334
+ * would anyway, whichever order the two halves ran in.
2335
+ *
2336
+ * Deferral is invisible to aggregate readers: every bucket- and extrema-backed
2337
+ * read path drains the queue first, so user code reading a count later in the
2338
+ * same mutation — including from a trigger firing mid-statement — still sees
2339
+ * its own writes.
2298
2340
  */
2299
2341
  declare const reconcileAggregateMembership: (db: GenericDatabaseWriter<any>, params: {
2300
2342
  tableName: string;
@@ -2755,6 +2797,7 @@ declare class ConvexDeleteBuilder<TTable extends ConvexTable<any>, TReturning ex
2755
2797
  }): this;
2756
2798
  executeAsync(this: ConvexDeleteExecutableThis<TTable, TReturning, TMode>, ...args: TMode extends 'single' ? [config?: Omit<MutationExecuteConfig, 'mode'>] : [config: never]): Promise<TMode extends 'single' ? MutationExecuteResult<TTable, TReturning, 'single'> : never>;
2757
2799
  execute(this: ConvexDeleteExecutableThis<TTable, TReturning, TMode>, ...args: TMode extends 'single' ? [config?: MutationExecuteConfig] : [config?: never]): Promise<MutationExecuteResult<TTable, TReturning, TMode>>;
2800
+ private _runStatement;
2758
2801
  }
2759
2802
  //#endregion
2760
2803
  //#region src/orm/insert.d.ts
@@ -2795,6 +2838,7 @@ declare class ConvexInsertBuilder<TTable extends ConvexTable<any>, TReturning ex
2795
2838
  onConflictDoNothing(config?: InsertOnConflictDoNothingConfig<TTable>): ConvexInsertWithout<this, 'onConflictDoNothing' | 'onConflictDoUpdate'>;
2796
2839
  onConflictDoUpdate(config: InsertOnConflictDoUpdateConfig<TTable>): ConvexInsertWithout<this, 'onConflictDoNothing' | 'onConflictDoUpdate'>;
2797
2840
  execute(): Promise<MutationResult<TTable, TReturning>>;
2841
+ private _runStatement;
2798
2842
  private resolveReturningRow;
2799
2843
  private handleConflict;
2800
2844
  private findConflictRow;
@@ -3065,8 +3109,41 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3065
3109
  private _finalizeRows;
3066
3110
  private _getSchemaDefinitionOrThrow;
3067
3111
  private _applyEqBounds;
3068
- private _buildTableFilterPredicate;
3069
- private _assertWhereIndexRequirement;
3112
+ /**
3113
+ * The row filter for an already-resolved pipeline stage `where`.
3114
+ *
3115
+ * Takes the resolved shape rather than the clause so the rows that are read
3116
+ * and the rows that are kept come from the same resolution. Resolving twice
3117
+ * would let a `where` callback that closes over changing state — a clock, a
3118
+ * counter — bound the read with one expression and filter it with another,
3119
+ * dropping rows that the second expression matched but the first never read.
3120
+ */
3121
+ private _buildResolvedWherePredicate;
3122
+ /**
3123
+ * What a pipeline stage `where` is, resolved once.
3124
+ *
3125
+ * Object clauses and callbacks that return an expression can be compiled and
3126
+ * therefore index-lowered. `predicate(...)` is opaque JavaScript and never
3127
+ * can be. Both the anchoring check and the index lowering need to tell those
3128
+ * apart, and a callback `where` should be run once per read rather than once
3129
+ * per caller that asks.
3130
+ */
3131
+ private _resolvePipelineWhere;
3132
+ /**
3133
+ * Reject a `predicate(...)` stage `where` on a read nothing bounds.
3134
+ *
3135
+ * A predicate can only run after the row is read, so it costs one read per
3136
+ * candidate row. That is fine over an index range and unbounded over a table
3137
+ * scan, which is the same line `findMany` draws for a chain-level
3138
+ * `predicate(...)`. An object `where` is not held to it: the compiler
3139
+ * lowers what it can and post-filters the rest, exactly as the chain-level
3140
+ * read does.
3141
+ *
3142
+ * `remedy` is per call site because the anchor a caller can add differs: a
3143
+ * union source takes an index, a `flatMap` stage has no index option at all
3144
+ * and is anchored by the relation's own declared index.
3145
+ */
3146
+ private _assertPipelineWhereIsAnchored;
3070
3147
  private _isFilterExpressionNode;
3071
3148
  private _isPredicateWhereClause;
3072
3149
  private _createFilterOperators;
@@ -3099,14 +3176,31 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3099
3176
  /**
3100
3177
  * The compiled index union, as one ordered stream.
3101
3178
  *
3102
- * Each probe is its own index range, so the union is only usable where the
3103
- * merged order is the order the read has to produce. `mergedStream` orders by
3104
- * a *suffix* of the index key, and a suffix may only drop key components the
3179
+ * Each probe is its own index range, so the union is only usable where its
3180
+ * order is the order the read has to produce. `mergedStream` orders by a
3181
+ * suffix of the index key, and a suffix may only drop key components the
3105
3182
  * probes all pin to a single value — so the requested field has to sit inside
3106
3183
  * the pinned run or immediately after it. Returns null when it does not, and
3107
3184
  * the caller falls back to the plan's plain index range or a bounded scan.
3185
+ *
3186
+ * Two executors, because a union that is too wide to merge is not too wide to
3187
+ * read. A merge registers every probe up front and holds them open for the
3188
+ * life of the read, which is what `MAX_INDEX_UNION_PROBES` refuses; past that
3189
+ * width the probes are concatenated instead, one open query at a time. What
3190
+ * the cap picks is therefore fan-out versus sequential — never index versus
3191
+ * table scan, which is a trade no read wins.
3108
3192
  */
3109
3193
  private _buildProbeUnionStream;
3194
+ /**
3195
+ * `probeFilters` in the order a scan of `indexField` reaches them, or null
3196
+ * when they cannot be proven to be pairwise-disjoint ranges on that field.
3197
+ *
3198
+ * Concatenation stands in for a merge only under those two properties, and
3199
+ * both have to come from the filters themselves rather than from trust in the
3200
+ * compiler that emitted them: overlapping probes would emit a row twice, and
3201
+ * probes handed over out of order would emit index keys backwards.
3202
+ */
3203
+ private _orderDisjointProbes;
3110
3204
  /**
3111
3205
  * The read the compiled plan describes, as a stream, with nothing filtered
3112
3206
  * yet.
@@ -3138,7 +3232,41 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3138
3232
  * the caller then falls back to its plain-query path.
3139
3233
  */
3140
3234
  private _buildResidualFilterStream;
3235
+ /**
3236
+ * One `pipeline.union` source, as a stream.
3237
+ *
3238
+ * The source's own `where` is compiled into an index plan first, so an
3239
+ * object `where` bounds the read instead of being discarded row by row after
3240
+ * it. Two rules keep that from changing what the union produces:
3241
+ *
3242
+ * - `_compileQueryPlan` discards a plan that would displace an index the
3243
+ * caller pinned, so a source's own range (a tenant scope, say) still wins.
3244
+ * - A merged union is ordered by `interleaveBy`, which only some index
3245
+ * shapes can supply. The lowered read is checked against that before it is
3246
+ * committed to, and the unlowered scan is used when it cannot serve the
3247
+ * merge — an index that saves reads is not worth an ordering that throws.
3248
+ *
3249
+ * The `where` stays applied in JS either way. The index range is a bound on
3250
+ * what is read, not a replacement for the predicate.
3251
+ */
3141
3252
  private _buildUnionSourceStream;
3253
+ /**
3254
+ * The index a `flatMap` stage `where` can ride on top of the join keys.
3255
+ *
3256
+ * The join is only correct while `targetFields` stay pinned by equality, and
3257
+ * a Convex range builder pins fields in index-key order with no gaps, so the
3258
+ * candidates are exactly the declared indexes that lead with `targetFields`
3259
+ * and carry at least one more field. Those trailing fields are what the
3260
+ * stage `where` is compiled against — the compiler never sees the join keys,
3261
+ * because their values are per parent and the index choice has to be made
3262
+ * once for all of them.
3263
+ *
3264
+ * Returns null when nothing is gained, and the caller keeps the relation's
3265
+ * own index plus the post-filter. A multi-probe plan is refused: each probe
3266
+ * is its own range, and a union of ranges per parent is a different stream
3267
+ * shape than the one `flatMap` was handed as `mappedIndexFields`.
3268
+ */
3269
+ private _resolveFlatMapStageIndex;
3142
3270
  private _applyFlatMapStage;
3143
3271
  private _applyPipelineStages;
3144
3272
  private _tryNativeUnfilteredCount;
@@ -3203,6 +3331,17 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3203
3331
  * Phase 4 implementation with WhereClauseCompiler
3204
3332
  */
3205
3333
  private _toConvexQuery;
3334
+ /**
3335
+ * The read plan for one `where` on this table.
3336
+ *
3337
+ * Split out of `_toConvexQuery` because the chain-level `where` is not the
3338
+ * only one that has to be index-lowered: a `pipeline.union` source carries
3339
+ * its own `where` and its own pinned index, and compiles against the same
3340
+ * table with a different ordering contract (`interleaveBy`, not `orderBy`).
3341
+ * Both go through here so a plain-object `where` is planned the same way
3342
+ * wherever it is written.
3343
+ */
3344
+ private _compileQueryPlan;
3206
3345
  private _buildRelationKey;
3207
3346
  /**
3208
3347
  * How many leading fields of the scanned index are pinned to a single value.
@@ -3634,6 +3773,7 @@ declare class ConvexUpdateBuilder<TTable extends ConvexTable<any>, TReturning ex
3634
3773
  allowFullScan(): ConvexUpdateBuilder<TTable, TReturning, TMode, true>;
3635
3774
  executeAsync(this: ConvexUpdateExecutableThis<TTable, TReturning, TMode>, ...args: TMode extends 'single' ? [config?: Omit<MutationExecuteConfig, 'mode'>] : [config: never]): Promise<TMode extends 'single' ? MutationExecuteResult<TTable, TReturning, 'single'> : never>;
3636
3775
  execute(this: ConvexUpdateExecutableThis<TTable, TReturning, TMode>, ...args: TMode extends 'single' ? [config?: MutationExecuteConfig] : [config?: never]): Promise<MutationExecuteResult<TTable, TReturning, TMode>>;
3776
+ private _runStatement;
3637
3777
  }
3638
3778
  //#endregion
3639
3779
  //#region src/orm/database.d.ts
@@ -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,8 +283,8 @@ 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;
@@ -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: {
@@ -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-BJm_VSDT.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-Bxzoofl4.js";
2
2
 
3
3
  //#region src/orm/aggregate-index/capability.d.ts
4
4
  /**