@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 37.4 kB
Source Map (JSON)
{"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n join as joinOperator,\n map,\n serializeValue,\n tap,\n} from '@tanstack/db-ivm'\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftSourceError,\n InvalidJoinConditionRightSourceError,\n InvalidJoinConditionSameSourceError,\n InvalidJoinConditionSourceMismatchError,\n JoinCollectionNotFoundError,\n UnsupportedFromTypeError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from '../../errors.js'\nimport { getParentContextIdentity } from '../equality-value-identity.js'\nimport { ensureIndexForField } from '../../indexes/auto-index.js'\nimport { validateJoinConditions } from '../join-conditions.js'\nimport {\n Value as ValClass,\n collectPropRefs,\n getFromSources,\n getPropRefSourceAlias,\n requireCollectionSource,\n} from '../ir.js'\nimport { compileExpression } from './evaluators.js'\nimport { getLazyLoadTargets } from './lazy-targets.js'\nimport { crossJoinParentRoutes } from './parent-routes.js'\nimport { queriesMatchForCaching } from './query-equivalence.js'\nimport {\n CALLBACK_INPUT,\n attachRouteMetadata,\n attachRouteMetadataToResult,\n getCallbackInput,\n getNamespacedRouteMetadata,\n wrapInputRow,\n} from './route-metadata.js'\nimport type { ValueIdentity } from '../equality-value-identity.js'\nimport type { CompilationResult, IncludesCompilationResult } from './index.js'\nimport type {\n BasicExpression,\n CollectionSourceRef,\n From,\n JoinClause,\n PropRef,\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 InputRow,\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from '../../types.js'\nimport type { QueryMapping } from './types.js'\n\ntype CompileSubquery = (\n query: QueryIR,\n parentKeyStream?: KeyedStream,\n) => CompilationResult\n\nexport type LazyDemandPlan = {\n id: string\n path: Array<string>\n collectionId: string\n initialKeys: Set<unknown>\n}\n\n/** Callbacks for managing lazy-loaded collections in optimized joins */\nexport type LazyCollectionCallbacks = {\n plans?: Array<LazyDemandPlan>\n setDemand?: (plan: LazyDemandPlan, keys: Set<unknown>) => void\n}\n\ntype JoinInputValue = [\n originalKey: unknown,\n namespacedRow: NamespacedRow,\n joinValue: unknown,\n]\n\nlet nextLazyDemandPlanId = 0\n\nfunction parameterizeJoinInputByParentRoutes(\n input: KeyedStream,\n parentKeyStream: KeyedStream,\n valueIdentity: ValueIdentity,\n): KeyedStream {\n return crossJoinParentRoutes(\n input,\n parentKeyStream,\n (rowKey, row, correlationKey, parentContext, callbackValue) => {\n return [\n serializeValue([\n valueIdentity.equality(rowKey),\n valueIdentity.equality(correlationKey),\n getParentContextIdentity(parentContext),\n ]),\n attachRouteMetadata(\n {\n ...(row as Record<string, unknown>),\n },\n correlationKey,\n parentContext,\n ),\n callbackValue,\n ] as unknown as [unknown, unknown]\n },\n )\n}\n\nfunction getRouteJoinKey(\n row: NamespacedRow,\n source: string,\n equalityKey: unknown,\n valueIdentity: ValueIdentity,\n): string {\n const route = getNamespacedRouteMetadata(row, source)\n return serializeValue([\n valueIdentity.equality(route?.correlationKey),\n getParentContextIdentity(route?.parentContext ?? null),\n equalityKey,\n ])\n}\n\nfunction keyJoinInput(\n originalKey: unknown,\n row: NamespacedRow,\n source: string,\n side: `main` | `joined`,\n expressions: Array<(row: NamespacedRow) => unknown>,\n routeJoinedSource: boolean,\n valueIdentity: ValueIdentity,\n): [string, JoinInputValue] {\n const value = expressions[0]!(row)\n const input: JoinInputValue = [originalKey, row, undefined]\n // NUL-prefixed side-local keys cannot collide with serializeValue output.\n // Unsatisfiable tuples retain their row but contribute no lazy demand.\n const unmatchedKey = side === `main` ? `\\0m` : `\\0j`\n if (value == null) return [unmatchedKey, input]\n let key = valueIdentity.equality(value)\n // Keep the single-equality path free of temporary operand arrays.\n if (expressions.length > 1) {\n const values = [key]\n for (let index = 1; index < expressions.length; index++) {\n const component = expressions[index]!(row)\n if (component == null) return [unmatchedKey, input]\n values.push(valueIdentity.equality(component))\n }\n key = values\n }\n // Demand uses the raw value, while matching uses graph-local equality.\n input[2] = value\n return [\n routeJoinedSource\n ? getRouteJoinKey(row, source, key, valueIdentity)\n : serializeValue(key),\n input,\n ]\n}\n\nexport function registerLazyDemandPlan(\n callbacks: Record<string, LazyCollectionCallbacks>,\n target: { sourceId: string; path: Array<string>; collection: Collection },\n initialKeys: Set<unknown> = new Set(),\n): LazyDemandPlan {\n const plan: LazyDemandPlan = {\n id: `lazy-demand-${++nextLazyDemandPlanId}`,\n path: target.path,\n collectionId: target.collection.id,\n initialKeys: new Set(initialKeys),\n }\n const state = (callbacks[target.sourceId] ??= {})\n ;(state.plans ??= []).push(plan)\n return plan\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 queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n rawQuery: QueryIR,\n localFrom: From,\n onCompileSubquery: CompileSubquery,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n mainSourceIsParentFiltered: boolean,\n valueIdentity: ValueIdentity,\n parentKeyStream?: KeyedStream,\n): NamespacedAndKeyedStream {\n const localBindings = new Set(\n getFromSources(localFrom).map((source) => source.bindingId),\n )\n\n // The loop owns the source bindings and compiler context for each join.\n function processJoin(\n inputPipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n ): NamespacedAndKeyedStream {\n const joinedSource = joinClause.from.alias\n const mainAliases = Object.keys(sources)\n const role = (ref: PropRef): JoinRefRole => {\n const alias = getJoinRefAlias(ref)\n if (ref.bindingId !== undefined) {\n if (ref.bindingId === joinClause.from.bindingId) return `joined`\n return localBindings.has(ref.bindingId) ? `main` : `ancestor`\n }\n if (alias === joinedSource) return `joined`\n if (\n alias &&\n (mainAliases.includes(alias) || rawQuery.from.type === `unionAll`)\n )\n return `main`\n return `unknown`\n }\n const conditions = validateJoinConditions(joinClause.on).map(\n ([left, right]) =>\n analyzeJoinExpressions(left, right, joinedSource, role),\n )\n // The first equality supplies candidate demand; the full tuple decides matches.\n const { mainExpr, joinedExpr } = conditions[0]!\n const joinedExpressionUsesParent = conditions.some(\n ({ joinedExpr: expression }) =>\n collectPropRefs(expression).some((ref) => {\n const sourceRole = role(ref)\n return sourceRole === `ancestor` || sourceRole === `unknown`\n }),\n )\n const routeJoinedSource =\n parentKeyStream !== undefined &&\n (joinClause.from.type === `queryRef` ||\n ((joinClause.type === `right` || joinClause.type === `full`) &&\n !mainSourceIsParentFiltered) ||\n joinedExpressionUsesParent)\n\n // Get the joined source alias and input stream\n const {\n alias: processedJoinedSource,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processSource(\n joinClause.from,\n allInputs,\n queryMapping,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n valueIdentity,\n `join`,\n routeJoinedSource ? parentKeyStream : undefined,\n )\n\n if (processedJoinedSource !== joinedSource) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // Add the joined source to the sources map\n sources[joinedSource] = joinedInput\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 sourceActivity = getActiveAndLazySources(\n joinClause.type,\n mainCollection,\n joinedCollection,\n mainSourceIsParentFiltered,\n )\n const activeSource = routeJoinedSource\n ? undefined\n : sourceActivity.activeSource\n const lazySource = sourceActivity.lazySource\n\n // Pre-compile the join expressions\n const compiledMainExpressions = conditions.map((condition) =>\n compileExpression(condition.mainExpr),\n )\n const compiledJoinedExpressions = conditions.map((condition) =>\n compileExpression(condition.joinedExpr),\n )\n\n // Prepare both sides with the same key and raw-demand rules.\n let mainPipeline = inputPipeline.pipe(\n map(([currentKey, namespacedRow]) =>\n keyJoinInput(\n currentKey,\n namespacedRow,\n mainSource,\n `main`,\n compiledMainExpressions,\n routeJoinedSource,\n valueIdentity,\n ),\n ),\n )\n\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row, callbackValue]: [...InputRow, unknown?]) =>\n keyJoinInput(\n currentKey,\n wrapInputRow(joinedSource, row, callbackValue),\n joinedSource,\n `joined`,\n compiledJoinedExpressions,\n routeJoinedSource,\n valueIdentity,\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 !== undefined ||\n lazyFrom.query.offset !== undefined ||\n lazyFrom.query.singleResult)\n const resultUnionLazySide = lazyFrom.type === `unionAll`\n\n const lazySourceJoinExpr = activeSource === `main` ? joinedExpr : mainExpr\n const lazyAlias = activeSource === `main` ? joinedSource : mainSource\n const lazyTargets = resultUnionLazySide\n ? []\n : getLazyLoadTargets(\n rawQuery,\n lazyFrom,\n lazyAlias,\n lazySourceJoinExpr,\n lazySource,\n aliasRemapping,\n )\n\n if (!limitedSubquery && lazyTargets.length > 0) {\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 aliases 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 for (const target of lazyTargets) {\n lazySources.add(target.sourceId)\n }\n\n const demandPlans = lazyTargets.map((target) =>\n registerLazyDemandPlan(callbacks, target),\n )\n const demandWeights = new Map<\n string,\n { key: unknown; weight: number }\n >()\n\n const activePipeline =\n activeSource === `main` ? mainPipeline : joinedPipeline\n\n for (const target of lazyTargets) {\n const fieldName = target.path[0]\n if (fieldName) {\n ensureIndexForField(fieldName, target.path, target.collection)\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: string, value: JoinInputValue]\n > = activePipeline.pipe(\n tap((data) => {\n for (const [\n [joinKey, [, , joinValue]],\n weight,\n ] of data.getInner()) {\n if (joinValue == null) continue\n const previous = demandWeights.get(joinKey)\n const nextWeight = (previous?.weight ?? 0) + weight\n if (nextWeight === 0) {\n demandWeights.delete(joinKey)\n } else {\n demandWeights.set(joinKey, {\n key: previous?.key ?? joinValue,\n weight: nextWeight,\n })\n }\n }\n\n const keys = new Set(\n [...demandWeights.values()]\n .filter(({ weight }) => weight > 0)\n .map(({ key }) => key),\n )\n for (let index = 0; index < lazyTargets.length; index++) {\n const target = lazyTargets[index]!\n callbacks[target.sourceId]?.setDemand?.(demandPlans[index]!, keys)\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(joinedSource, mainAliases),\n )\n }\n\n for (const joinClause of joinClauses) {\n pipeline = processJoin(pipeline, joinClause)\n localBindings.add(joinClause.from.bindingId)\n }\n\n return pipeline\n}\n\ntype JoinRefRole = `joined` | `main` | `ancestor` | `unknown`\n\nfunction getJoinRefAlias(ref: PropRef): string | undefined {\n return getPropRefSourceAlias(ref) ?? ref.path[0]\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 joinedSource: string,\n role: (ref: PropRef) => JoinRefRole,\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n const leftRefs = collectPropRefs(left)\n const rightRefs = collectPropRefs(right)\n const leftRoles = new Set(leftRefs.map(role))\n const rightRoles = new Set(rightRefs.map(role))\n const leftMain = leftRoles.has(`main`) || leftRoles.has(`ancestor`)\n const rightMain = rightRoles.has(`main`) || rightRoles.has(`ancestor`)\n const leftJoined = leftRoles.has(`joined`)\n const rightJoined = rightRoles.has(`joined`)\n\n // If left expression refers to an available source and right refers to joined source, keep as is\n if (leftMain && !leftJoined && rightJoined && !rightRoles.has(`main`)) {\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 (leftJoined && !leftRoles.has(`main`) && rightMain && !rightJoined) {\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 (leftRefs.length === 0 || rightRefs.length === 0) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (\n leftRefs.length === 1 &&\n rightRefs.length === 1 &&\n (leftRefs[0]!.bindingId !== undefined &&\n rightRefs[0]!.bindingId !== undefined\n ? leftRefs[0]!.bindingId === rightRefs[0]!.bindingId\n : getJoinRefAlias(leftRefs[0]!) === getJoinRefAlias(rightRefs[0]!))\n ) {\n throw new InvalidJoinConditionSameSourceError(\n getJoinRefAlias(leftRefs[0]!)!,\n )\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 (!leftMain) {\n throw new InvalidJoinConditionLeftSourceError(\n getJoinRefAlias(leftRefs[0]!)!,\n )\n }\n\n // Right side must refer to the joined source\n if (!rightJoined) {\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 * Compiles a FROM or JOIN source and propagates its source metadata.\n */\nexport function processSource(\n from: CollectionSourceRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileSubquery,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n valueIdentity: ValueIdentity,\n sourceKind: `from` | `join`,\n parentKeyStream?: KeyedStream,\n): {\n alias: string\n input: KeyedStream\n collectionId: string\n includes?: Array<IncludesCompilationResult>\n} {\n switch (from.type) {\n case `descriptorRef`:\n return requireCollectionSource(from)\n case `collectionRef`: {\n const input = allInputs[from.sourceId]\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 {\n alias: from.alias,\n input:\n sourceKind === `join` && parentKeyStream\n ? parameterizeJoinInputByParentRoutes(\n input,\n parentKeyStream,\n valueIdentity,\n )\n : input,\n collectionId: from.collection.id,\n }\n }\n case `queryRef`: {\n // Preserve the user-defined query as the cache key when optimization\n // only copied it. If the optimizer changed the query, compile that IR;\n // substituting its origin would discard pushed predicates.\n const originalQuery = queryMapping.get(from.query)\n const queryToCompile =\n originalQuery && queriesMatchForCaching(from.query, originalQuery)\n ? originalQuery\n : from.query\n const subQueryResult = onCompileSubquery(queryToCompile, parentKeyStream)\n\n Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)\n Object.assign(aliasRemapping, subQueryResult.aliasRemapping)\n // Optimizer-created wrappers already contributed their predicates to the\n // parent. User-authored subqueries must export their own source predicates.\n if (\n queryMapping.has(from.query) ||\n from.alias !== getFromSources(from.query.from)[0]?.alias\n ) {\n for (const [alias, where] of subQueryResult.sourceWhereClauses)\n sourceWhereClauses.set(alias, where)\n }\n // Resolve directly to the innermost alias, so nested scopes need one lookup.\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 // 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 [\n key,\n [\n value,\n _orderByIndex,\n correlationKey,\n parentContext,\n publicKey,\n callbackValue,\n ],\n ] = data\n if (sourceKind === `join` && !parentKeyStream) {\n return [key, value, callbackValue] as unknown as InputRow\n }\n return [\n key,\n attachRouteMetadataToResult(\n sourceKind === `from` && value instanceof ValClass\n ? value.value\n : value,\n correlationKey,\n parentContext,\n publicKey,\n ),\n callbackValue,\n ] as unknown as InputRow\n }),\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n includes: subQueryResult.includes,\n }\n }\n default:\n throw sourceKind === `join`\n ? new UnsupportedJoinSourceTypeError((from as any).type)\n : new UnsupportedFromTypeError((from as any).type)\n }\n}\n\n// JSON prints non-finite numbers as null, the missing-side marker. An object\n// never collides with a source key, which is a string or a number.\nfunction encodeNonFiniteKey(_: string, value: unknown): unknown {\n return typeof value === `number` && !Number.isFinite(value)\n ? { number: String(value) }\n : value\n}\n\nfunction processJoinResults(\n joinedSource: string,\n mainAliases: ReadonlyArray<string>,\n): (\n pipeline: IStreamBuilder<\n [key: string, [JoinInputValue | undefined, JoinInputValue | undefined]]\n >,\n) => NamespacedAndKeyedStream {\n return (pipeline) =>\n pipeline.pipe(\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 // The main side owns inherited parent aliases. Missing local sides\n // must not expose an ancestor with the same alias.\n const mergeRows = (\n mainRow: NamespacedRow | undefined,\n joinedRow: NamespacedRow | undefined,\n ): NamespacedRow => {\n const row = { ...(mainRow ?? joinedRow) } as NamespacedRow\n if (main == null) for (const alias of mainAliases) delete row[alias]\n if (joinedRow) row[joinedSource] = joinedRow[joinedSource]!\n else delete row[joinedSource]\n return row\n }\n const mergedNamespacedRow = mergeRows(\n mainNamespacedRow,\n joinedNamespacedRow,\n )\n const mainView =\n mainNamespacedRow && getCallbackInput(mainNamespacedRow)\n const joinedView =\n joinedNamespacedRow && getCallbackInput(joinedNamespacedRow)\n if (\n mainView !== mainNamespacedRow ||\n joinedView !== joinedNamespacedRow\n ) {\n Object.assign(mergedNamespacedRow, {\n [CALLBACK_INPUT]: mergeRows(mainView, joinedView),\n })\n }\n\n // Combine the main and joined keys without ambiguity: keys may contain\n // delimiters, numbers and strings may print alike, and a missing outer\n // side encodes as null, which no source key can be.\n const resultKey = JSON.stringify(\n [mainKey ?? null, joinedKey ?? null],\n encodeNonFiniteKey,\n )\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\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 mainSourceIsParentFiltered: boolean,\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 // A correlated include has already reduced the main relation to active\n // parent routes. Keep that relation active and load the joined side from\n // its keys; reversing the join would create an independent demand plan\n // that widens the correlated source back to a union of constraints.\n if (mainSourceIsParentFiltered) {\n return { activeSource: `main`, lazySource: rightCollection }\n }\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":["crossJoinParentRoutes","serializeValue","getParentContextIdentity","attachRouteMetadata","getNamespacedRouteMetadata","getFromSources","validateJoinConditions","collectPropRefs","InvalidJoinConditionSourceMismatchError","JoinCollectionNotFoundError","compileExpression","map","wrapInputRow","UnsupportedJoinTypeError","lazyTargets","getLazyLoadTargets","ensureIndexForField","tap","joinOperator","getPropRefSourceAlias","InvalidJoinConditionSameSourceError","InvalidJoinConditionLeftSourceError","InvalidJoinConditionRightSourceError","InvalidJoinCondition","requireCollectionSource","CollectionInputNotFoundError","queriesMatchForCaching","attachRouteMetadataToResult","ValClass","UnsupportedJoinSourceTypeError","UnsupportedFromTypeError","getCallbackInput","CALLBACK_INPUT"],"mappings":"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"}