kitcn 0.32.0 → 0.32.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,25 @@
1
1
  # kitcn
2
2
 
3
+ ## 0.32.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#427](https://github.com/udecode/kitcn/pull/427) [`92381f5`](https://github.com/udecode/kitcn/commit/92381f56a660dfcd4679674f612e64c1518bcf75) Thanks [@MikeyZhang75](https://github.com/MikeyZhang75)! - ## Patches
8
+
9
+ - Improve the read cost of filtering parents by whether a related child exists.
10
+ `where: { posts: { type: 'wanted' } }` now stops at the first matching child
11
+ instead of loading the whole per-parent child window and testing it afterwards.
12
+ On one parent with 61 posts and a match sorting first, that is 2 document reads
13
+ instead of 62 — the same cost as the hand-written
14
+ `with: { posts: { where: { type: 'wanted' }, limit: 1 } }`. The same applies to
15
+ `where: { posts: true }`, to a relation existence test under `NOT`, and to
16
+ `through` relations, where it also stops at the first matching junction row.
17
+ Results are unchanged: the filter still decides from the same window it read
18
+ before, so a match past `defaultLimit` is excluded exactly as it was.
19
+ - A relation named more than once in one `where` — across `OR`, `AND` and `NOT`
20
+ branches — keeps the previous single unbounded load, because those branches
21
+ share it and each needs to read it in full.
22
+
3
23
  ## 0.32.0
4
24
 
5
25
  ### Minor Changes
@@ -1,5 +1,5 @@
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";
1
+ import { Vn as ConvexTextBuilderInitial, Zt as ConvexTableWithColumns } from "../capabilities-Ctcw2VRq.js";
2
+ import { C as ConvexNumberBuilderInitial, E as ConvexIdBuilderInitial, N as ConvexCustomBuilderInitial } from "../where-clause-compiler-BHGHbfb9.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";
@@ -3014,7 +3014,21 @@ declare class GelRelationalQuery<TSchema extends TablesRelationalConfig, TTableC
3014
3014
  private _buildFieldFilterExpression;
3015
3015
  private _buildFilterExpression;
3016
3016
  private _mergeWithConfig;
3017
+ /**
3018
+ * Relation keys the filter tree mentions exactly once, across every OR/AND/NOT
3019
+ * branch at this table level.
3020
+ *
3021
+ * `_mergeWithConfig` collapses the whole tree into one load per relation key,
3022
+ * so a key two branches disagree about has to be loaded the way both branches
3023
+ * can read. Only a key with a single occurrence has one predicate to satisfy,
3024
+ * and only then can that predicate be pushed into the read plan.
3025
+ *
3026
+ * Relation values are not descended into: they belong to the target table and
3027
+ * get their own count when that level is lowered.
3028
+ */
3029
+ private _collectSingleOccurrenceRelations;
3017
3030
  private _buildFilterWithConfig;
3031
+ private _buildFilterWithConfigForLevel;
3018
3032
  private _stripFilterRelations;
3019
3033
  private _hasSearchDisallowedRelationFilter;
3020
3034
  private _searchFilterValuesEqual;
@@ -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-CD-Ij91k.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-Ctcw2VRq.js";
2
2
 
3
3
  //#region src/orm/aggregate-index/capability.d.ts
4
4
  /**
@@ -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-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";
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-Ctcw2VRq.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-BHGHbfb9.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";
package/dist/orm/index.js CHANGED
@@ -1322,6 +1322,20 @@ const DEFAULT_RELATION_FAN_OUT_MAX_KEYS = 1e3;
1322
1322
  * satisfies the limit.
1323
1323
  */
1324
1324
  const RELATION_FILTER_STREAM_CHUNK = 32;
1325
+ /**
1326
+ * Marks a relation config that only exists to answer "does a matching child
1327
+ * exist?" for a `where` clause. The lowered load may stop at the first
1328
+ * surviving child, but it must not widen the window it walks to find one --
1329
+ * that window is what decides the answer, so changing it would change results
1330
+ * rather than just their cost.
1331
+ *
1332
+ * A symbol, not a field: the emitted object is fed to the same relation loader
1333
+ * that serves a caller's `with`, and nothing a caller can write should be able
1334
+ * to claim this.
1335
+ */
1336
+ const RELATION_EXISTENCE_PROBE = Symbol("kitcn.relationExistenceProbe");
1337
+ /** Lowering a subtree with this set emits no existence probes anywhere in it. */
1338
+ const NO_PROBE_RELATIONS = /* @__PURE__ */ new Set();
1325
1339
  const DEFAULT_AGGREGATE_CARTESIAN_MAX_KEYS = 4096;
1326
1340
  const DEFAULT_AGGREGATE_WORK_BUDGET = 16384;
1327
1341
  const PUBLIC_ID_FIELD = "id";
@@ -2391,7 +2405,47 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
2391
2405
  }
2392
2406
  }
2393
2407
  }
2408
+ /**
2409
+ * Relation keys the filter tree mentions exactly once, across every OR/AND/NOT
2410
+ * branch at this table level.
2411
+ *
2412
+ * `_mergeWithConfig` collapses the whole tree into one load per relation key,
2413
+ * so a key two branches disagree about has to be loaded the way both branches
2414
+ * can read. Only a key with a single occurrence has one predicate to satisfy,
2415
+ * and only then can that predicate be pushed into the read plan.
2416
+ *
2417
+ * Relation values are not descended into: they belong to the target table and
2418
+ * get their own count when that level is lowered.
2419
+ */
2420
+ _collectSingleOccurrenceRelations(filter, tableConfig) {
2421
+ const counts = /* @__PURE__ */ new Map();
2422
+ const walk = (node) => {
2423
+ if (!this._isRecord(node)) return;
2424
+ for (const [key, value] of Object.entries(node)) {
2425
+ if (value === void 0) continue;
2426
+ if (key === "OR" || key === "AND") {
2427
+ if (!Array.isArray(value)) continue;
2428
+ for (const sub of value) walk(sub);
2429
+ continue;
2430
+ }
2431
+ if (key === "NOT") {
2432
+ walk(value);
2433
+ continue;
2434
+ }
2435
+ if (!tableConfig.relations[key]) continue;
2436
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2437
+ }
2438
+ };
2439
+ walk(filter);
2440
+ const single = /* @__PURE__ */ new Set();
2441
+ for (const [key, count] of counts) if (count === 1) single.add(key);
2442
+ return single;
2443
+ }
2394
2444
  _buildFilterWithConfig(filter, tableConfig) {
2445
+ if (!this._isRecord(filter)) return {};
2446
+ return this._buildFilterWithConfigForLevel(filter, tableConfig, this._collectSingleOccurrenceRelations(filter, tableConfig));
2447
+ }
2448
+ _buildFilterWithConfigForLevel(filter, tableConfig, probeEligible) {
2395
2449
  if (!this._isRecord(filter)) return {};
2396
2450
  const result = {};
2397
2451
  const entries = Object.entries(filter);
@@ -2401,27 +2455,36 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
2401
2455
  if (key === "OR" || key === "AND") {
2402
2456
  if (!Array.isArray(value) || value.length === 0) continue;
2403
2457
  for (const sub of value) {
2404
- const nested = this._buildFilterWithConfig(sub, tableConfig);
2458
+ const nested = this._buildFilterWithConfigForLevel(sub, tableConfig, probeEligible);
2405
2459
  this._mergeWithConfig(result, nested);
2406
2460
  }
2407
2461
  continue;
2408
2462
  }
2409
2463
  if (key === "NOT") {
2410
- const nested = this._buildFilterWithConfig(value, tableConfig);
2464
+ const nested = this._buildFilterWithConfigForLevel(value, tableConfig, probeEligible);
2411
2465
  this._mergeWithConfig(result, nested);
2412
2466
  continue;
2413
2467
  }
2414
2468
  this._assertNoLegacyPublicFieldName(key);
2415
2469
  const relation = tableConfig.relations[key];
2416
2470
  if (!relation) continue;
2471
+ const probe = relation.relationType !== "one" && probeEligible.has(key) ? { [RELATION_EXISTENCE_PROBE]: true } : void 0;
2417
2472
  if (typeof value === "boolean") {
2418
- result[key] = true;
2473
+ result[key] = probe ?? true;
2419
2474
  continue;
2420
2475
  }
2421
2476
  const targetTableConfig = this._getTableConfigByDbName(relation.targetTableName);
2422
2477
  if (!targetTableConfig) continue;
2423
- const nested = this._buildFilterWithConfig(value, targetTableConfig);
2424
- result[key] = Object.keys(nested).length > 0 ? { with: nested } : true;
2478
+ const nested = this._buildFilterWithConfigForLevel(value, targetTableConfig, probe ? this._collectSingleOccurrenceRelations(value, targetTableConfig) : NO_PROBE_RELATIONS);
2479
+ const hasNested = Object.keys(nested).length > 0;
2480
+ if (!probe) {
2481
+ result[key] = hasNested ? { with: nested } : true;
2482
+ continue;
2483
+ }
2484
+ const emitted = { ...probe };
2485
+ if (Object.keys(value).length > 0) emitted.where = value;
2486
+ if (hasNested) emitted.with = nested;
2487
+ result[key] = emitted;
2425
2488
  }
2426
2489
  return result;
2427
2490
  }
@@ -4955,9 +5018,10 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
4955
5018
  * parents in the round.
4956
5019
  */
4957
5020
  async _readBoundedThroughLinks(params) {
4958
- const { applyTargetFilters, edge, enforceTargetKeyCap, entries, fetchLimit, fetchTargets, targetFields, throughIndexName, throughTableConfig } = params;
5021
+ const { applyAmbientTargetFilters, applyConfigTargetFilter, edge, enforceTargetKeyCap, entries, fetchLimit, fetchTargets, scanLimit, targetFields, throughIndexName, throughTableConfig } = params;
4959
5022
  const throughTargetFields = edge.through.targetFields;
4960
5023
  const cursors = entries.map(([key, values]) => ({
5024
+ ambientLinks: 0,
4961
5025
  buffered: [],
4962
5026
  exhausted: false,
4963
5027
  iterator: this._queryByFields(this.db.query(edge.through.table), edge.through.sourceFields, values, throughIndexName)[Symbol.asyncIterator](),
@@ -4984,10 +5048,12 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
4984
5048
  };
4985
5049
  /** Target key -> the surviving document, absent when it did not survive. */
4986
5050
  const survivorByKey = /* @__PURE__ */ new Map();
5051
+ /** Target keys that cleared the ambient filters, survivors or not. */
5052
+ const ambientKeys = /* @__PURE__ */ new Set();
4987
5053
  const resolvedKeys = /* @__PURE__ */ new Set();
4988
5054
  const survivors = [];
4989
5055
  while (true) {
4990
- const active = cursors.filter((cursor) => cursor.links.length < fetchLimit && !(cursor.exhausted && cursor.buffered.length === 0));
5056
+ const active = cursors.filter((cursor) => cursor.links.length < fetchLimit && (scanLimit === void 0 || cursor.ambientLinks < scanLimit) && !(cursor.exhausted && cursor.buffered.length === 0));
4991
5057
  if (active.length === 0) break;
4992
5058
  const candidatesPerCursor = await this._mapWithConcurrency(active, async (cursor) => {
4993
5059
  const need = fetchLimit - cursor.links.length;
@@ -5006,7 +5072,12 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5006
5072
  enforceTargetKeyCap(resolvedKeys.size + newKeys.size);
5007
5073
  const fetched = await fetchTargets(Array.from(newKeys.entries()));
5008
5074
  for (const key of newKeys.keys()) resolvedKeys.add(key);
5009
- const surviving = await applyTargetFilters(fetched.map((entry) => entry.target).filter((target) => !!target));
5075
+ const ambientTargets = await applyAmbientTargetFilters(fetched.map((entry) => entry.target).filter((target) => !!target));
5076
+ for (const target of ambientTargets) {
5077
+ const key = this._buildRelationKey(target, targetFields);
5078
+ if (key) ambientKeys.add(key);
5079
+ }
5080
+ const surviving = await applyConfigTargetFilter(ambientTargets);
5010
5081
  for (const target of surviving) {
5011
5082
  const key = this._buildRelationKey(target, targetFields);
5012
5083
  if (!key || survivorByKey.has(key)) continue;
@@ -5018,8 +5089,11 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5018
5089
  const cursor = active[i];
5019
5090
  for (const link of candidatesPerCursor[i]) {
5020
5091
  if (cursor.links.length >= fetchLimit) break;
5092
+ if (scanLimit !== void 0 && cursor.ambientLinks >= scanLimit) break;
5021
5093
  const key = this._buildRelationKey(link, throughTargetFields);
5022
- if (!key || !survivorByKey.has(key)) continue;
5094
+ if (!key) continue;
5095
+ if (ambientKeys.has(key)) cursor.ambientLinks += 1;
5096
+ if (!survivorByKey.has(key)) continue;
5023
5097
  cursor.links.push(link);
5024
5098
  }
5025
5099
  }
@@ -5072,6 +5146,7 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5072
5146
  const relationDefinition = tableConfig.relations[relationName];
5073
5147
  const strict = tableConfig.strict !== false;
5074
5148
  const hasPostFetchTargetFilter = this.rls?.mode !== "skip" && isRlsEnabled(targetTableConfig.table) || Boolean(relationDefinition?.where) || Boolean(relationConfig && typeof relationConfig === "object" && "where" in relationConfig && relationConfig.where);
5149
+ const isExistenceProbe = Boolean(relationConfig) && typeof relationConfig === "object" && relationConfig[RELATION_EXISTENCE_PROBE] === true;
5075
5150
  let orderSpecs = [];
5076
5151
  if (relationConfig && typeof relationConfig === "object" && "orderBy" in relationConfig) {
5077
5152
  let orderByValue = relationConfig.orderBy;
@@ -5086,15 +5161,22 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5086
5161
  if (effectivePerParentLimit === void 0 && !this.allowFullScan) throw new Error(`Relation "${tableConfig.name}.${relationName}" requires limit, allowFullScan: true, or defineSchema(..., { defaults: { defaultLimit } }).`);
5087
5162
  const perParentOffset = relationConfig && typeof relationConfig === "object" && "offset" in relationConfig ? relationConfig.offset : void 0;
5088
5163
  if (perParentOffset !== void 0 && typeof perParentOffset !== "number") throw new Error("Only numeric offset is supported in kitcn ORM.");
5164
+ const probeFetchLimit = isExistenceProbe ? 1 : void 0;
5165
+ const probeScanLimit = isExistenceProbe ? effectivePerParentLimit : void 0;
5166
+ const canBoundPerParentRead = effectivePerParentLimit !== void 0 || isExistenceProbe;
5089
5167
  const applyOffsetAndLimit = (items) => {
5090
5168
  let result = items;
5091
5169
  if (perParentOffset !== void 0 && perParentOffset > 0) result = result.slice(perParentOffset);
5092
5170
  if (effectivePerParentLimit !== void 0) result = result.slice(0, effectivePerParentLimit);
5093
5171
  return result;
5094
5172
  };
5095
- const applyPostFetchTargetFilters = async (candidateTargets) => {
5096
- let filteredTargets = await this._applyRlsSelectFilter(candidateTargets, targetTableConfig);
5097
- if (relationDefinition?.where) filteredTargets = filteredTargets.filter((target) => this._evaluateTableFilter(target, targetTableConfig, relationDefinition.where));
5173
+ const applyAmbientTargetFilters = async (candidateTargets) => {
5174
+ const visibleTargets = await this._applyRlsSelectFilter(candidateTargets, targetTableConfig);
5175
+ if (!relationDefinition?.where) return visibleTargets;
5176
+ return visibleTargets.filter((target) => this._evaluateTableFilter(target, targetTableConfig, relationDefinition.where));
5177
+ };
5178
+ const applyConfigTargetFilter = async (ambientTargets) => {
5179
+ let filteredTargets = ambientTargets;
5098
5180
  if (relationConfig && typeof relationConfig === "object" && "where" in relationConfig) {
5099
5181
  const whereFilter = relationConfig.where;
5100
5182
  if (typeof whereFilter === "function") {
@@ -5107,6 +5189,7 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5107
5189
  }
5108
5190
  return filteredTargets;
5109
5191
  };
5192
+ const applyPostFetchTargetFilters = async (candidateTargets) => applyConfigTargetFilter(await applyAmbientTargetFilters(candidateTargets));
5110
5193
  let targets = [];
5111
5194
  let throughBySourceKey;
5112
5195
  let targetFiltersApplied = false;
@@ -5141,14 +5224,16 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5141
5224
  };
5142
5225
  });
5143
5226
  };
5144
- if (orderSpecs.length === 0 && effectivePerParentLimit !== void 0) {
5227
+ if (orderSpecs.length === 0 && canBoundPerParentRead) {
5145
5228
  const bounded = await this._readBoundedThroughLinks({
5146
- applyTargetFilters: applyPostFetchTargetFilters,
5229
+ applyAmbientTargetFilters,
5230
+ applyConfigTargetFilter,
5147
5231
  edge,
5148
5232
  enforceTargetKeyCap,
5149
5233
  entries,
5150
- fetchLimit: Math.max(perParentOffset ?? 0, 0) + effectivePerParentLimit,
5234
+ fetchLimit: probeFetchLimit ?? Math.max(perParentOffset ?? 0, 0) + effectivePerParentLimit,
5151
5235
  fetchTargets: fetchThroughTargets,
5236
+ scanLimit: probeScanLimit,
5152
5237
  targetFields,
5153
5238
  throughIndexName,
5154
5239
  throughTableConfig
@@ -5188,22 +5273,25 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5188
5273
  });
5189
5274
  const orderServedByIndex = orderSpecs.length === 0 || orderPushdownDirection !== null;
5190
5275
  const applyPushdownOrder = (query) => orderPushdownDirection ? query.order(orderPushdownDirection) : query;
5191
- const streamPostFetchTargetFilters = orderServedByIndex && hasPostFetchTargetFilter && effectivePerParentLimit !== void 0;
5276
+ const streamPostFetchTargetFilters = orderServedByIndex && hasPostFetchTargetFilter && canBoundPerParentRead;
5192
5277
  targetFiltersApplied = streamPostFetchTargetFilters;
5193
5278
  targets = (await this._mapWithConcurrency(entries, async ([, values]) => {
5194
5279
  const query = this._queryByFields(this.db.query(edge.targetTable), targetFields, values, indexName);
5195
- if (orderServedByIndex && !hasPostFetchTargetFilter && effectivePerParentLimit !== void 0) {
5196
- const fetchLimit = (perParentOffset ?? 0) + (effectivePerParentLimit ?? 0);
5280
+ if (orderServedByIndex && !hasPostFetchTargetFilter && canBoundPerParentRead) {
5281
+ const fetchLimit = probeFetchLimit ?? (perParentOffset ?? 0) + (effectivePerParentLimit ?? 0);
5197
5282
  return await applyPushdownOrder(query).take(fetchLimit);
5198
5283
  }
5199
5284
  if (streamPostFetchTargetFilters) {
5200
5285
  const visibleTargets = [];
5201
- const fetchLimit = Math.max(perParentOffset ?? 0, 0) + (effectivePerParentLimit ?? 0);
5286
+ const fetchLimit = probeFetchLimit ?? Math.max(perParentOffset ?? 0, 0) + (effectivePerParentLimit ?? 0);
5202
5287
  let batch = [];
5288
+ let ambientSeen = 0;
5203
5289
  const drain = async () => {
5204
5290
  if (batch.length === 0) return;
5205
- const filtered = await applyPostFetchTargetFilters(batch);
5291
+ const ambientTargets = await applyAmbientTargetFilters(batch);
5206
5292
  batch = [];
5293
+ ambientSeen += ambientTargets.length;
5294
+ const filtered = await applyConfigTargetFilter(ambientTargets);
5207
5295
  for (const row of filtered) {
5208
5296
  if (visibleTargets.length >= fetchLimit) return;
5209
5297
  visibleTargets.push(row);
@@ -5215,6 +5303,7 @@ var GelRelationalQuery = class GelRelationalQuery extends QueryPromise {
5215
5303
  if (batch.length < chunk) continue;
5216
5304
  await drain();
5217
5305
  if (visibleTargets.length >= fetchLimit) break;
5306
+ if (probeScanLimit !== void 0 && ambientSeen >= probeScanLimit) break;
5218
5307
  }
5219
5308
  if (visibleTargets.length < fetchLimit) await drain();
5220
5309
  return visibleTargets;
@@ -1,3 +1,3 @@
1
- import { C as MigrationStep, D as defineMigration, E as buildMigrationPlan, O as defineMigrationSet, S as MigrationStateMap, T as MigrationWriteMode, _ as MigrationManifestEntry, a as MAX_STATUS_RUN_LIMIT, b as MigrationRunStatus, c as MigrationRunChunkArgs, d as MigrationAppliedState, f as MigrationDefinition, g as MigrationDriftIssue, h as MigrationDocContext, k as detectMigrationDrift, l as MigrationStatusArgs, m as MigrationDoc, o as MigrationCancelArgs, p as MigrationDirection, s as MigrationRunArgs, u as createMigrationHandlers, v as MigrationMigrateOne, w as MigrationTableName, x as MigrationSet, y as MigrationPlan } from "../../capabilities-CD-Ij91k.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-FDJTqLDC.js";
1
+ import { C as MigrationStep, D as defineMigration, E as buildMigrationPlan, O as defineMigrationSet, S as MigrationStateMap, T as MigrationWriteMode, _ as MigrationManifestEntry, a as MAX_STATUS_RUN_LIMIT, b as MigrationRunStatus, c as MigrationRunChunkArgs, d as MigrationAppliedState, f as MigrationDefinition, g as MigrationDriftIssue, h as MigrationDocContext, k as detectMigrationDrift, l as MigrationStatusArgs, m as MigrationDoc, o as MigrationCancelArgs, p as MigrationDirection, s as MigrationRunArgs, u as createMigrationHandlers, v as MigrationMigrateOne, w as MigrationTableName, x as MigrationSet, y as MigrationPlan } from "../../capabilities-Ctcw2VRq.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-BHGHbfb9.js";
3
3
  export { MAX_STATUS_RUN_LIMIT, MIGRATION_RUN_TABLE, MIGRATION_STATE_TABLE, MIGRATION_STORAGE_TABLE_NAMES, type MigrationAppliedState, type MigrationCancelArgs, type MigrationDefinition, type MigrationDirection, type MigrationDoc, type MigrationDocContext, type MigrationDriftIssue, type MigrationManifestEntry, type MigrationMigrateOne, type MigrationPlan, type MigrationRunArgs, type MigrationRunChunkArgs, type MigrationRunStatus, type MigrationSet, type MigrationStateMap, type MigrationStatusArgs, type MigrationStep, type MigrationTableName, type MigrationWriteMode, buildMigrationPlan, createMigrationHandlers, defineMigration, defineMigrationSet, detectMigrationDrift, injectMigrationStorageTables, migrationCapability, migrationExtension, migrationStorageTables };
@@ -1,4 +1,4 @@
1
- import { $t as OrmLifecycleChange, A as CreateDatabaseOptions, Ar as $Type, B as getUniqueIndexes, Ct as OrderByClause, F as CascadeMode, Ft as ExtractTablesFromSchema, Gn as Columns, Gt as TablesRelationalConfig, Ht as RelationsBuilderConfigValue, I as DeleteMode, Kn as OrmRuntimeDefaults, L as SerializedFilterExpression, Mr as ColumnBuilder, Mt as VectorSearchProvider, N as OrmReader, Nr as ColumnBuilderBaseConfig, Or as SystemFieldAliases, P as OrmWriter, Pt as AnyRelationsBuilderConfig, Qn as OrmSchemaTriggers, R as getChecks, U as EdgeMetadata, Un as ConvexColumnBuilder, Ur as entityKind, Ut as RelationsConfigWithSchema, Vn as ConvexTextBuilderInitial, Wt as TableRelationalConfig, Xt as ConvexTable, Yn as OrmSchemaExtensionTriggers, Zn as OrmSchemaRelations, Zt as ConvexTableWithColumns, an as RlsPolicy, dt as InferInsertModel, kr as SystemFields, pt as InferSelectModel, qn as OrmSchemaExtensionRelations, r as OrmCapability, rr as FilterExpression$1, x as MigrationSet, z as getForeignKeys, zr as HasDefault, zt as RelationsBuilder } from "./capabilities-CD-Ij91k.js";
1
+ import { $t as OrmLifecycleChange, A as CreateDatabaseOptions, Ar as $Type, B as getUniqueIndexes, Ct as OrderByClause, F as CascadeMode, Ft as ExtractTablesFromSchema, Gn as Columns, Gt as TablesRelationalConfig, Ht as RelationsBuilderConfigValue, I as DeleteMode, Kn as OrmRuntimeDefaults, L as SerializedFilterExpression, Mr as ColumnBuilder, Mt as VectorSearchProvider, N as OrmReader, Nr as ColumnBuilderBaseConfig, Or as SystemFieldAliases, P as OrmWriter, Pt as AnyRelationsBuilderConfig, Qn as OrmSchemaTriggers, R as getChecks, U as EdgeMetadata, Un as ConvexColumnBuilder, Ur as entityKind, Ut as RelationsConfigWithSchema, Vn as ConvexTextBuilderInitial, Wt as TableRelationalConfig, Xt as ConvexTable, Yn as OrmSchemaExtensionTriggers, Zn as OrmSchemaRelations, Zt as ConvexTableWithColumns, an as RlsPolicy, dt as InferInsertModel, kr as SystemFields, pt as InferSelectModel, qn as OrmSchemaExtensionRelations, r as OrmCapability, rr as FilterExpression$1, x as MigrationSet, z as getForeignKeys, zr as HasDefault, zt as RelationsBuilder } from "./capabilities-Ctcw2VRq.js";
2
2
  import * as convex_values0 from "convex/values";
3
3
  import { GenericId, Validator, Value } from "convex/values";
4
4
  import { DefineSchemaOptions, GenericDatabaseReader, GenericDatabaseWriter, GenericSchema, SchedulableFunctionReference, Scheduler, SchemaDefinition, internalActionGeneric, internalMutationGeneric, internalQueryGeneric } from "convex/server";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",