@tanstack/db 0.6.0 → 0.6.2

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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const ir = require("./query/ir.cjs");
4
- const index$1 = require("./collection/index.cjs");
4
+ const index = require("./collection/index.cjs");
5
5
  const SortedMap = require("./SortedMap.cjs");
6
6
  const transactions = require("./transactions.cjs");
7
7
  const proxy = require("./proxy.cjs");
@@ -21,19 +21,19 @@ const indexRegistry = require("./indexes/index-registry.cjs");
21
21
  const expressionHelpers = require("./query/expression-helpers.cjs");
22
22
  const effect = require("./query/effect.cjs");
23
23
  const functions = require("./query/builder/functions.cjs");
24
- const index = require("./query/builder/index.cjs");
25
- const subsetDedupe = require("./query/subset-dedupe.cjs");
26
- const evaluators = require("./query/compiler/evaluators.cjs");
24
+ const index$1 = require("./query/builder/index.cjs");
27
25
  const index$2 = require("./query/compiler/index.cjs");
26
+ const evaluators = require("./query/compiler/evaluators.cjs");
28
27
  const liveQueryCollection = require("./query/live-query-collection.cjs");
29
- const debounceStrategy = require("./strategies/debounceStrategy.cjs");
30
- const predicateUtils = require("./query/predicate-utils.cjs");
31
28
  const queryOnce = require("./query/query-once.cjs");
29
+ const predicateUtils = require("./query/predicate-utils.cjs");
30
+ const subsetDedupe = require("./query/subset-dedupe.cjs");
31
+ const debounceStrategy = require("./strategies/debounceStrategy.cjs");
32
32
  const queueStrategy = require("./strategies/queueStrategy.cjs");
33
33
  const throttleStrategy = require("./strategies/throttleStrategy.cjs");
34
34
  exports.IR = ir;
35
- exports.CollectionImpl = index$1.CollectionImpl;
36
- exports.createCollection = index$1.createCollection;
35
+ exports.CollectionImpl = index.CollectionImpl;
36
+ exports.createCollection = index.createCollection;
37
37
  exports.SortedMap = SortedMap.SortedMap;
38
38
  exports.createTransaction = transactions.createTransaction;
39
39
  exports.getActiveTransaction = transactions.getActiveTransaction;
@@ -180,16 +180,15 @@ exports.or = functions.or;
180
180
  exports.sum = functions.sum;
181
181
  exports.toArray = functions.toArray;
182
182
  exports.upper = functions.upper;
183
- exports.BaseQueryBuilder = index.BaseQueryBuilder;
184
- exports.Query = index.Query;
185
- exports.DeduplicatedLoadSubset = subsetDedupe.DeduplicatedLoadSubset;
183
+ exports.BaseQueryBuilder = index$1.BaseQueryBuilder;
184
+ exports.Query = index$1.Query;
185
+ exports.compileQuery = index$2.compileQuery;
186
186
  exports.compileExpression = evaluators.compileExpression;
187
187
  exports.compileSingleRowExpression = evaluators.compileSingleRowExpression;
188
188
  exports.toBooleanPredicate = evaluators.toBooleanPredicate;
189
- exports.compileQuery = index$2.compileQuery;
190
189
  exports.createLiveQueryCollection = liveQueryCollection.createLiveQueryCollection;
191
190
  exports.liveQueryCollectionOptions = liveQueryCollection.liveQueryCollectionOptions;
192
- exports.debounceStrategy = debounceStrategy.debounceStrategy;
191
+ exports.queryOnce = queryOnce.queryOnce;
193
192
  exports.isLimitSubset = predicateUtils.isLimitSubset;
194
193
  exports.isOffsetLimitSubset = predicateUtils.isOffsetLimitSubset;
195
194
  exports.isOrderBySubset = predicateUtils.isOrderBySubset;
@@ -197,7 +196,8 @@ exports.isPredicateSubset = predicateUtils.isPredicateSubset;
197
196
  exports.isWhereSubset = predicateUtils.isWhereSubset;
198
197
  exports.minusWherePredicates = predicateUtils.minusWherePredicates;
199
198
  exports.unionWherePredicates = predicateUtils.unionWherePredicates;
200
- exports.queryOnce = queryOnce.queryOnce;
199
+ exports.DeduplicatedLoadSubset = subsetDedupe.DeduplicatedLoadSubset;
200
+ exports.debounceStrategy = debounceStrategy.debounceStrategy;
201
201
  exports.queueStrategy = queueStrategy.queueStrategy;
202
202
  exports.throttleStrategy = throttleStrategy.throttleStrategy;
203
203
  //# sourceMappingURL=index.cjs.map
@@ -136,7 +136,14 @@ function processJoin(pipeline, joinClause, sources, mainCollectionId, mainSource
136
136
  if (lazySourceSubscription.hasLoadedInitialState()) {
137
137
  return;
138
138
  }
139
- const joinKeys = data.getInner().map(([[joinKey]]) => joinKey);
139
+ const joinKeys = [
140
+ ...new Set(
141
+ data.getInner().map(([[joinKey]]) => joinKey).filter((key) => key != null)
142
+ )
143
+ ];
144
+ if (joinKeys.length === 0) {
145
+ return;
146
+ }
140
147
  const lazyJoinRef = new ir.PropRef(followRefResult.path);
141
148
  const loaded = lazySourceSubscription.requestSnapshot({
142
149
  where: functions.inArray(lazyJoinRef, joinKeys),
@@ -1 +1 @@
1
- {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import { filter, join as joinOperator, map, tap } from '@tanstack/db-ivm'\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftSourceError,\n InvalidJoinConditionRightSourceError,\n InvalidJoinConditionSameSourceError,\n InvalidJoinConditionSourceMismatchError,\n JoinCollectionNotFoundError,\n SubscriptionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from '../../errors.js'\nimport { normalizeValue } from '../../utils/comparison.js'\nimport { ensureIndexForField } from '../../indexes/auto-index.js'\nimport { PropRef, followRef } from '../ir.js'\nimport { inArray } from '../builder/functions.js'\nimport { compileExpression } from './evaluators.js'\nimport type { CompileQueryFn } from './index.js'\nimport type { OrderByOptimizationInfo } from './order-by.js'\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryIR,\n QueryRef,\n} from '../ir.js'\nimport type { IStreamBuilder, JoinType } from '@tanstack/db-ivm'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from '../../types.js'\nimport type { QueryCache, QueryMapping, WindowOptions } from './types.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\n\n/** Function type for loading specific keys into a lazy collection */\nexport type LoadKeysFn = (key: Set<string | number>) => void\n\n/** Callbacks for managing lazy-loaded collections in optimized joins */\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses, applying lazy loading optimizations and maintaining\n * alias tracking for per-alias subscriptions (enables self-joins).\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n sources,\n mainCollectionId,\n mainSource,\n allInputs,\n cache,\n queryMapping,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n rawQuery,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause with lazy loading optimization.\n * For LEFT/RIGHT/INNER joins, marks one side as \"lazy\" (loads on-demand based on join keys).\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): NamespacedAndKeyedStream {\n const isCollectionRef = joinClause.from.type === `collectionRef`\n\n // Get the joined source alias and input stream\n const {\n alias: joinedSource,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n )\n\n // Add the joined source to the sources map\n sources[joinedSource] = joinedInput\n if (isCollectionRef) {\n // Only direct collection references form new alias bindings. Subquery\n // aliases reuse the mapping returned from the recursive compilation above.\n aliasToCollectionId[joinedSource] = joinedCollectionId\n }\n\n const mainCollection = collections[mainCollectionId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainCollectionId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeSource, lazySource } = getActiveAndLazySources(\n joinClause.type,\n mainCollection,\n joinedCollection,\n )\n\n // Analyze which source each expression refers to and swap if necessary\n const availableSources = Object.keys(sources)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableSources,\n joinedSource,\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main source expression\n const mainKey = normalizeValue(compiledMainExpr(namespacedRow))\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n }),\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedSource]: row }\n\n // Extract the join key from the joined source expression\n const joinedKey = normalizeValue(compiledJoinedExpr(namespacedRow))\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n }),\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeSource) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n // If join expressions contain computed values (like concat functions)\n // we don't optimize the join because we don't have an index over the computed values\n const hasComputedJoinExpr =\n mainExpr.type === `func` || joinedExpr.type === `func`\n\n if (!limitedSubquery && !hasComputedJoinExpr) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy source alias as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which source aliases should load data lazily (not initially)\n const lazyAlias = activeSource === `main` ? joinedSource : mainSource\n lazySources.add(lazyAlias)\n\n const activePipeline =\n activeSource === `main` ? mainPipeline : joinedPipeline\n\n const lazySourceJoinExpr =\n activeSource === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazySourceJoinExpr,\n lazySource,\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n )\n }\n\n // Set up lazy loading: intercept active side's stream and dynamically load\n // matching rows from lazy side based on join keys.\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n // Find the subscription for lazy loading.\n // Subscriptions are keyed by the innermost alias (where the collection subscription\n // was actually created). For subqueries, the join alias may differ from the inner alias.\n // aliasRemapping provides a flattened one-hop lookup from outer → innermost alias.\n // Example: .join({ activeUser: subquery }) where subquery uses .from({ user: collection })\n // → aliasRemapping['activeUser'] = 'user' (always maps directly to innermost, never recursive)\n const resolvedAlias = aliasRemapping[lazyAlias] || lazyAlias\n const lazySourceSubscription = subscriptions[resolvedAlias]\n\n if (!lazySourceSubscription) {\n throw new SubscriptionNotFoundError(\n resolvedAlias,\n lazyAlias,\n lazySource.id,\n Object.keys(subscriptions),\n )\n }\n\n if (lazySourceSubscription.hasLoadedInitialState()) {\n // Entire state was already loaded because we deoptimized the join\n return\n }\n\n // Request filtered snapshot from lazy collection for matching join keys\n const joinKeys = data.getInner().map(([[joinKey]]) => joinKey)\n const lazyJoinRef = new PropRef(followRefResult.path)\n const loaded = lazySourceSubscription.requestSnapshot({\n where: inArray(lazyJoinRef, joinKeys),\n optimizedOnly: true,\n })\n\n if (!loaded) {\n // Snapshot wasn't sent because it could not be loaded from the indexes\n lazySourceSubscription.requestSnapshot()\n }\n }),\n )\n\n if (activeSource === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n processJoinResults(joinClause.type),\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which source\n * and returns them in the correct order (available source expression first, joined source expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableSourceAliases: Array<string>,\n joinedSource: string,\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined source alias from the available source aliases\n const availableSources = allAvailableSourceAliases.filter(\n (alias) => alias !== joinedSource,\n )\n\n const leftSourceAlias = getSourceAliasFromExpression(left)\n const rightSourceAlias = getSourceAliasFromExpression(right)\n\n // If left expression refers to an available source and right refers to joined source, keep as is\n if (\n leftSourceAlias &&\n availableSources.includes(leftSourceAlias) &&\n rightSourceAlias === joinedSource\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined source and right refers to an available source, swap them\n if (\n leftSourceAlias === joinedSource &&\n rightSourceAlias &&\n availableSources.includes(rightSourceAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any source, this is an invalid join\n if (!leftSourceAlias || !rightSourceAlias) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftSourceAlias === rightSourceAlias) {\n throw new InvalidJoinConditionSameSourceError(leftSourceAlias)\n }\n\n // Left side must refer to an available source\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable source, but just in case, but could happen with the IR\n if (!availableSources.includes(leftSourceAlias)) {\n throw new InvalidJoinConditionLeftSourceError(leftSourceAlias)\n }\n\n // Right side must refer to the joined source\n if (rightSourceAlias !== joinedSource) {\n throw new InvalidJoinConditionRightSourceError(joinedSource)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the source alias from a join expression\n */\nfunction getSourceAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the source alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same source\n const sourceAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getSourceAliasFromExpression(arg)\n if (alias) {\n sourceAliases.add(alias)\n }\n }\n // If all arguments refer to the same source, return that source alias\n return sourceAliases.size === 1 ? Array.from(sourceAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any source\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n cache: QueryCache,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.alias]\n if (!input) {\n throw new CollectionInputNotFoundError(\n from.alias,\n from.collection.id,\n Object.keys(allInputs),\n )\n }\n aliasToCollectionId[from.alias] = from.collection.id\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = onCompileSubquery(\n originalQuery,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n )\n\n // Pull up alias mappings from subquery to parent scope.\n // This includes both the innermost alias-to-collection mappings AND\n // any existing remappings from nested subquery levels.\n Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)\n Object.assign(aliasRemapping, subQueryResult.aliasRemapping)\n\n // Pull up source WHERE clauses from subquery to parent scope.\n // This enables loadSubset to receive the correct where clauses for subquery collections.\n //\n // IMPORTANT: Skip pull-up for optimizer-created subqueries. These are detected when:\n // 1. The outer alias (from.alias) matches the inner alias (from.query.from.alias)\n // 2. The subquery was found in queryMapping (it's a user-defined subquery, not optimizer-created)\n //\n // For optimizer-created subqueries, the parent already has the sourceWhereClauses\n // extracted from the original raw query, so pulling up would be redundant.\n const isUserDefinedSubquery = queryMapping.has(from.query)\n const fromInnerAlias = from.query.from.alias\n const isOptimizerCreated =\n !isUserDefinedSubquery && from.alias === fromInnerAlias\n\n if (!isOptimizerCreated) {\n for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {\n sourceWhereClauses.set(alias, whereClause)\n }\n }\n\n // Create a flattened remapping from outer alias to innermost alias.\n // For nested subqueries, this ensures one-hop lookups (not recursive chains).\n //\n // Example with 3-level nesting:\n // Inner: .from({ user: usersCollection })\n // Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user\n // Outer: .join({ author: middleSubquery }, ...) → creates: author → user (not author → activeUser)\n //\n // We search through the PULLED-UP aliasToCollectionId (which contains the\n // innermost 'user' alias), so we always map directly to the deepest level.\n // This means aliasRemapping[lazyAlias] is always a single lookup, never recursive.\n const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(\n (alias) =>\n subQueryResult.aliasToCollectionId[alias] ===\n subQueryResult.collectionId,\n )\n if (innerAlias && innerAlias !== from.alias) {\n aliasRemapping[from.alias] = innerAlias\n }\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n }),\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >,\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n }),\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main source (i.e. left collection) or the joined source (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazySources(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection,\n):\n | { activeSource: `main` | `joined`; lazySource: Collection }\n | { activeSource: undefined; lazySource: undefined } {\n // Self-joins can now be optimized since we track lazy loading by source alias\n // rather than collection ID. Each alias has its own subscription and lazy state.\n\n switch (joinType) {\n case `left`:\n return { activeSource: `main`, lazySource: rightCollection }\n case `right`:\n return { activeSource: `joined`, lazySource: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeSource: `main`, lazySource: rightCollection }\n : { activeSource: `joined`, lazySource: leftCollection }\n default:\n return { activeSource: undefined, lazySource: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","normalizeValue","UnsupportedJoinTypeError","followRef","ensureIndexForField","tap","SubscriptionNotFoundError","PropRef","inArray","joinOperator","InvalidJoinConditionSourceMismatchError","InvalidJoinConditionSameSourceError","InvalidJoinConditionLeftSourceError","InvalidJoinConditionRightSourceError","InvalidJoinCondition","CollectionInputNotFoundError","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
1
+ {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import { filter, join as joinOperator, map, tap } from '@tanstack/db-ivm'\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftSourceError,\n InvalidJoinConditionRightSourceError,\n InvalidJoinConditionSameSourceError,\n InvalidJoinConditionSourceMismatchError,\n JoinCollectionNotFoundError,\n SubscriptionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from '../../errors.js'\nimport { normalizeValue } from '../../utils/comparison.js'\nimport { ensureIndexForField } from '../../indexes/auto-index.js'\nimport { PropRef, followRef } from '../ir.js'\nimport { inArray } from '../builder/functions.js'\nimport { compileExpression } from './evaluators.js'\nimport type { CompileQueryFn } from './index.js'\nimport type { OrderByOptimizationInfo } from './order-by.js'\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryIR,\n QueryRef,\n} from '../ir.js'\nimport type { IStreamBuilder, JoinType } from '@tanstack/db-ivm'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from '../../types.js'\nimport type { QueryCache, QueryMapping, WindowOptions } from './types.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\n\n/** Function type for loading specific keys into a lazy collection */\nexport type LoadKeysFn = (key: Set<string | number>) => void\n\n/** Callbacks for managing lazy-loaded collections in optimized joins */\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses, applying lazy loading optimizations and maintaining\n * alias tracking for per-alias subscriptions (enables self-joins).\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n sources,\n mainCollectionId,\n mainSource,\n allInputs,\n cache,\n queryMapping,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n rawQuery,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause with lazy loading optimization.\n * For LEFT/RIGHT/INNER joins, marks one side as \"lazy\" (loads on-demand based on join keys).\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): NamespacedAndKeyedStream {\n const isCollectionRef = joinClause.from.type === `collectionRef`\n\n // Get the joined source alias and input stream\n const {\n alias: joinedSource,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n )\n\n // Add the joined source to the sources map\n sources[joinedSource] = joinedInput\n if (isCollectionRef) {\n // Only direct collection references form new alias bindings. Subquery\n // aliases reuse the mapping returned from the recursive compilation above.\n aliasToCollectionId[joinedSource] = joinedCollectionId\n }\n\n const mainCollection = collections[mainCollectionId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainCollectionId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeSource, lazySource } = getActiveAndLazySources(\n joinClause.type,\n mainCollection,\n joinedCollection,\n )\n\n // Analyze which source each expression refers to and swap if necessary\n const availableSources = Object.keys(sources)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableSources,\n joinedSource,\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main source expression\n const mainKey = normalizeValue(compiledMainExpr(namespacedRow))\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n }),\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedSource]: row }\n\n // Extract the join key from the joined source expression\n const joinedKey = normalizeValue(compiledJoinedExpr(namespacedRow))\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n }),\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeSource) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n // If join expressions contain computed values (like concat functions)\n // we don't optimize the join because we don't have an index over the computed values\n const hasComputedJoinExpr =\n mainExpr.type === `func` || joinedExpr.type === `func`\n\n if (!limitedSubquery && !hasComputedJoinExpr) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy source alias as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which source aliases should load data lazily (not initially)\n const lazyAlias = activeSource === `main` ? joinedSource : mainSource\n lazySources.add(lazyAlias)\n\n const activePipeline =\n activeSource === `main` ? mainPipeline : joinedPipeline\n\n const lazySourceJoinExpr =\n activeSource === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazySourceJoinExpr,\n lazySource,\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n )\n }\n\n // Set up lazy loading: intercept active side's stream and dynamically load\n // matching rows from lazy side based on join keys.\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n // Find the subscription for lazy loading.\n // Subscriptions are keyed by the innermost alias (where the collection subscription\n // was actually created). For subqueries, the join alias may differ from the inner alias.\n // aliasRemapping provides a flattened one-hop lookup from outer → innermost alias.\n // Example: .join({ activeUser: subquery }) where subquery uses .from({ user: collection })\n // → aliasRemapping['activeUser'] = 'user' (always maps directly to innermost, never recursive)\n const resolvedAlias = aliasRemapping[lazyAlias] || lazyAlias\n const lazySourceSubscription = subscriptions[resolvedAlias]\n\n if (!lazySourceSubscription) {\n throw new SubscriptionNotFoundError(\n resolvedAlias,\n lazyAlias,\n lazySource.id,\n Object.keys(subscriptions),\n )\n }\n\n if (lazySourceSubscription.hasLoadedInitialState()) {\n // Entire state was already loaded because we deoptimized the join\n return\n }\n\n // Deduplicate and filter null keys before requesting snapshot\n const joinKeys = [\n ...new Set(\n data\n .getInner()\n .map(([[joinKey]]) => joinKey)\n .filter((key) => key != null),\n ),\n ]\n\n if (joinKeys.length === 0) {\n return\n }\n\n const lazyJoinRef = new PropRef(followRefResult.path)\n const loaded = lazySourceSubscription.requestSnapshot({\n where: inArray(lazyJoinRef, joinKeys),\n optimizedOnly: true,\n })\n\n if (!loaded) {\n // Snapshot wasn't sent because it could not be loaded from the indexes\n lazySourceSubscription.requestSnapshot()\n }\n }),\n )\n\n if (activeSource === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n processJoinResults(joinClause.type),\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which source\n * and returns them in the correct order (available source expression first, joined source expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableSourceAliases: Array<string>,\n joinedSource: string,\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined source alias from the available source aliases\n const availableSources = allAvailableSourceAliases.filter(\n (alias) => alias !== joinedSource,\n )\n\n const leftSourceAlias = getSourceAliasFromExpression(left)\n const rightSourceAlias = getSourceAliasFromExpression(right)\n\n // If left expression refers to an available source and right refers to joined source, keep as is\n if (\n leftSourceAlias &&\n availableSources.includes(leftSourceAlias) &&\n rightSourceAlias === joinedSource\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined source and right refers to an available source, swap them\n if (\n leftSourceAlias === joinedSource &&\n rightSourceAlias &&\n availableSources.includes(rightSourceAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any source, this is an invalid join\n if (!leftSourceAlias || !rightSourceAlias) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftSourceAlias === rightSourceAlias) {\n throw new InvalidJoinConditionSameSourceError(leftSourceAlias)\n }\n\n // Left side must refer to an available source\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable source, but just in case, but could happen with the IR\n if (!availableSources.includes(leftSourceAlias)) {\n throw new InvalidJoinConditionLeftSourceError(leftSourceAlias)\n }\n\n // Right side must refer to the joined source\n if (rightSourceAlias !== joinedSource) {\n throw new InvalidJoinConditionRightSourceError(joinedSource)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the source alias from a join expression\n */\nfunction getSourceAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the source alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same source\n const sourceAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getSourceAliasFromExpression(arg)\n if (alias) {\n sourceAliases.add(alias)\n }\n }\n // If all arguments refer to the same source, return that source alias\n return sourceAliases.size === 1 ? Array.from(sourceAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any source\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n cache: QueryCache,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.alias]\n if (!input) {\n throw new CollectionInputNotFoundError(\n from.alias,\n from.collection.id,\n Object.keys(allInputs),\n )\n }\n aliasToCollectionId[from.alias] = from.collection.id\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = onCompileSubquery(\n originalQuery,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n )\n\n // Pull up alias mappings from subquery to parent scope.\n // This includes both the innermost alias-to-collection mappings AND\n // any existing remappings from nested subquery levels.\n Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)\n Object.assign(aliasRemapping, subQueryResult.aliasRemapping)\n\n // Pull up source WHERE clauses from subquery to parent scope.\n // This enables loadSubset to receive the correct where clauses for subquery collections.\n //\n // IMPORTANT: Skip pull-up for optimizer-created subqueries. These are detected when:\n // 1. The outer alias (from.alias) matches the inner alias (from.query.from.alias)\n // 2. The subquery was found in queryMapping (it's a user-defined subquery, not optimizer-created)\n //\n // For optimizer-created subqueries, the parent already has the sourceWhereClauses\n // extracted from the original raw query, so pulling up would be redundant.\n const isUserDefinedSubquery = queryMapping.has(from.query)\n const fromInnerAlias = from.query.from.alias\n const isOptimizerCreated =\n !isUserDefinedSubquery && from.alias === fromInnerAlias\n\n if (!isOptimizerCreated) {\n for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {\n sourceWhereClauses.set(alias, whereClause)\n }\n }\n\n // Create a flattened remapping from outer alias to innermost alias.\n // For nested subqueries, this ensures one-hop lookups (not recursive chains).\n //\n // Example with 3-level nesting:\n // Inner: .from({ user: usersCollection })\n // Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user\n // Outer: .join({ author: middleSubquery }, ...) → creates: author → user (not author → activeUser)\n //\n // We search through the PULLED-UP aliasToCollectionId (which contains the\n // innermost 'user' alias), so we always map directly to the deepest level.\n // This means aliasRemapping[lazyAlias] is always a single lookup, never recursive.\n const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(\n (alias) =>\n subQueryResult.aliasToCollectionId[alias] ===\n subQueryResult.collectionId,\n )\n if (innerAlias && innerAlias !== from.alias) {\n aliasRemapping[from.alias] = innerAlias\n }\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n }),\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >,\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n }),\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main source (i.e. left collection) or the joined source (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazySources(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection,\n):\n | { activeSource: `main` | `joined`; lazySource: Collection }\n | { activeSource: undefined; lazySource: undefined } {\n // Self-joins can now be optimized since we track lazy loading by source alias\n // rather than collection ID. Each alias has its own subscription and lazy state.\n\n switch (joinType) {\n case `left`:\n return { activeSource: `main`, lazySource: rightCollection }\n case `right`:\n return { activeSource: `joined`, lazySource: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeSource: `main`, lazySource: rightCollection }\n : { activeSource: `joined`, lazySource: leftCollection }\n default:\n return { activeSource: undefined, lazySource: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","normalizeValue","UnsupportedJoinTypeError","followRef","ensureIndexForField","tap","SubscriptionNotFoundError","PropRef","inArray","joinOperator","InvalidJoinConditionSourceMismatchError","InvalidJoinConditionSameSourceError","InvalidJoinConditionLeftSourceError","InvalidJoinConditionRightSourceError","InvalidJoinCondition","CollectionInputNotFoundError","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
@@ -65,7 +65,7 @@ class CollectionConfigBuilder {
65
65
  getConfig() {
66
66
  return {
67
67
  id: this.id,
68
- getKey: this.config.getKey || ((item) => this.resultKeys.get(item)),
68
+ getKey: this.config.getKey || ((item) => this.resultKeys.get(item) ?? item.$key),
69
69
  sync: this.getSyncConfig(),
70
70
  compare: this.compare,
71
71
  defaultStringCollation: this.compareOptions,
@@ -901,7 +901,8 @@ function createChildCollectionEntry(parentId, fieldName, correlationKey, hasOrde
901
901
  };
902
902
  }
903
903
  },
904
- startSync: true
904
+ startSync: true,
905
+ gcTime: 0
905
906
  });
906
907
  const entry = {
907
908
  collection,