@tanstack/db 0.5.25 → 0.5.26

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 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from '../../collection/index.js'\nimport {\n Aggregate as AggregateExpr,\n CollectionRef,\n Func as FuncExpr,\n PropRef,\n QueryRef,\n Value as ValueExpr,\n isExpressionLike,\n} from '../ir.js'\nimport {\n InvalidSourceError,\n InvalidSourceTypeError,\n InvalidWhereExpressionError,\n JoinConditionMustBeEqualityError,\n OnlyOneSourceAllowedError,\n QueryMustHaveFromClauseError,\n SubQueryMustHaveFromClauseError,\n} from '../../errors.js'\nimport {\n createRefProxy,\n createRefProxyWithSelected,\n toExpression,\n} from './ref-proxy.js'\nimport type { NamespacedRow, SingleResult } from '../../types.js'\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByDirection,\n QueryIR,\n} from '../ir.js'\nimport type {\n CompareOptions,\n Context,\n FunctionalHavingRow,\n GetResult,\n GroupByCallback,\n JoinOnCallback,\n MergeContextForJoinCallback,\n MergeContextWithJoinType,\n OrderByCallback,\n OrderByOptions,\n RefsForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from './types.js'\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string,\n ): [string, CollectionRef | QueryRef] {\n // Validate source is a plain object (not null, array, string, etc.)\n // We use try-catch to handle null/undefined gracefully\n let keys: Array<string>\n try {\n keys = Object.keys(source)\n } catch {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const type = source === null ? `null` : `undefined`\n throw new InvalidSourceTypeError(context, type)\n }\n\n // Check if it's an array (arrays pass Object.keys but aren't valid sources)\n if (Array.isArray(source)) {\n throw new InvalidSourceTypeError(context, `array`)\n }\n\n // Validate exactly one key\n if (keys.length !== 1) {\n if (keys.length === 0) {\n throw new InvalidSourceTypeError(context, `empty object`)\n }\n // Check if it looks like a string was passed (has numeric keys)\n if (keys.every((k) => !isNaN(Number(k)))) {\n throw new InvalidSourceTypeError(context, `string`)\n }\n throw new OnlyOneSourceAllowedError(context)\n }\n\n const alias = keys[0]!\n const sourceValue = source[alias]\n\n // Validate the value is a Collection or QueryBuilder\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new SubQueryMustHaveFromClauseError(context)\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new InvalidSourceError(alias)\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource,\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefsForContext<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new JoinConditionMustBeEqualityError()\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Perform a LEFT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the left joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .leftJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n leftJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `left`>\n > {\n return this.join(source, onCallback, `left`)\n }\n\n /**\n * Perform a RIGHT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the right joined table available\n *\n * @example\n * ```ts\n * // Right join users with posts\n * query\n * .from({ users: usersCollection })\n * .rightJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n rightJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `right`>\n > {\n return this.join(source, onCallback, `right`)\n }\n\n /**\n * Perform an INNER JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the inner joined table available\n *\n * @example\n * ```ts\n * // Inner join users with posts\n * query\n * .from({ users: usersCollection })\n * .innerJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n innerJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `inner`>\n > {\n return this.join(source, onCallback, `inner`)\n }\n\n /**\n * Perform a FULL JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the full joined table available\n *\n * @example\n * ```ts\n * // Full join users with posts\n * query\n * .from({ users: usersCollection })\n * .fullJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n fullJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `full`>\n > {\n return this.join(source, onCallback, `full`)\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const expression = callback(refProxy)\n\n // Validate that the callback returned a valid expression\n // This catches common mistakes like using JavaScript comparison operators (===, !==, etc.)\n // which return boolean primitives instead of expression objects\n if (!isExpressionLike(expression)) {\n throw new InvalidWhereExpressionError(getValueTypeName(expression))\n }\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n // Add $selected namespace if SELECT clause exists (either regular or functional)\n const refProxy = (\n this.query.select || this.query.fnSelect\n ? createRefProxyWithSelected(aliases)\n : createRefProxy(aliases)\n ) as RefsForContext<TContext>\n const expression = callback(refProxy)\n\n // Validate that the callback returned a valid expression\n // This catches common mistakes like using JavaScript comparison operators (===, !==, etc.)\n // which return boolean primitives instead of expression objects\n if (!isExpressionLike(expression)) {\n throw new InvalidWhereExpressionError(getValueTypeName(expression))\n }\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefsForContext<TContext>) => TSelectObject,\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const selectObject = callback(refProxy)\n const select = buildNestedSelect(selectObject)\n\n return new BaseQueryBuilder({\n ...this.query,\n select: select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n options: OrderByDirection | OrderByOptions = `asc`,\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n // Add $selected namespace if SELECT clause exists (either regular or functional)\n const refProxy = (\n this.query.select || this.query.fnSelect\n ? createRefProxyWithSelected(aliases)\n : createRefProxy(aliases)\n ) as RefsForContext<TContext>\n const result = callback(refProxy)\n\n const opts: CompareOptions =\n typeof options === `string`\n ? { direction: options, nulls: `first` }\n : {\n direction: options.direction ?? `asc`,\n nulls: options.nulls ?? `first`,\n stringSort: options.stringSort,\n locale:\n options.stringSort === `locale` ? options.locale : undefined,\n localeOptions:\n options.stringSort === `locale`\n ? options.localeOptions\n : undefined,\n }\n\n const makeOrderByClause = (res: any) => {\n return {\n expression: toExpression(res),\n compareOptions: opts,\n }\n }\n\n // Create the new OrderBy structure with expression and direction\n const orderByClauses = Array.isArray(result)\n ? result.map((r) => makeOrderByClause(r))\n : [makeOrderByClause(result)]\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, ...orderByClauses],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Extend existing groupBy expressions (multiple groupBy calls should accumulate)\n const existingGroupBy = this.query.groupBy || []\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: [...existingGroupBy, ...newExpressions],\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n /**\n * Specify that the query should return distinct rows.\n * Deduplicates rows based on the selected columns.\n * @returns A QueryBuilder with distinct enabled\n *\n * @example\n * ```ts\n * // Get countries our users are from\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => users.country)\n * .distinct()\n * ```\n */\n distinct(): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n distinct: true,\n }) as any\n }\n\n /**\n * Specify that the query should return a single result\n * @returns A QueryBuilder that returns the first result\n *\n * @example\n * ```ts\n * // Get the user matching the query\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => eq(users.id, 1))\n * .findOne()\n *```\n */\n findOne(): QueryBuilder<TContext & SingleResult> {\n return new BaseQueryBuilder({\n ...this.query,\n // TODO: enforcing return only one result with also a default orderBy if none is specified\n // limit: 1,\n singleResult: true,\n })\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult,\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any,\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row (with $selected when select() was called) and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts}) => ({ userId: posts.userId, count: count(posts.id) }))\n * .fn.having(({ $selected }) => $selected.count > 5)\n * ```\n */\n having(\n callback: (row: FunctionalHavingRow<TContext>) => any,\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new QueryMustHaveFromClauseError()\n }\n return this.query as QueryIR\n }\n}\n\n// Helper to get a descriptive type name for error messages\nfunction getValueTypeName(value: unknown): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value === `object`) return `object`\n return typeof value\n}\n\n// Helper to ensure we have a BasicExpression/Aggregate for a value\nfunction toExpr(value: any): BasicExpression | Aggregate {\n if (value === undefined) return toExpression(null)\n if (\n value instanceof AggregateExpr ||\n value instanceof FuncExpr ||\n value instanceof PropRef ||\n value instanceof ValueExpr\n ) {\n return value as BasicExpression | Aggregate\n }\n return toExpression(value)\n}\n\nfunction isPlainObject(value: any): value is Record<string, any> {\n return (\n value !== null &&\n typeof value === `object` &&\n !isExpressionLike(value) &&\n !value.__refProxy\n )\n}\n\nfunction buildNestedSelect(obj: any): any {\n if (!isPlainObject(obj)) return toExpr(obj)\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (typeof k === `string` && k.startsWith(`__SPREAD_SENTINEL__`)) {\n // Preserve sentinel key and its value (value is unimportant at compile time)\n out[k] = v\n continue\n }\n out[k] = buildNestedSelect(v)\n }\n return out\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>,\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder,\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Helper type to extract the result type from a QueryBuilder (similar to Zod's z.infer)\nexport type QueryResult<T> = GetResult<ExtractContext<T>>\n\n// Export the types from types.ts for convenience\nexport type {\n Context,\n Source,\n GetResult,\n RefLeaf as Ref,\n InferResultType,\n} from './types.js'\n"],"names":["InvalidSourceTypeError","OnlyOneSourceAllowedError","CollectionImpl","CollectionRef","SubQueryMustHaveFromClauseError","QueryRef","InvalidSourceError","refProxy","createRefProxy","JoinConditionMustBeEqualityError","isExpressionLike","InvalidWhereExpressionError","createRefProxyWithSelected","toExpression","QueryMustHaveFromClauseError","AggregateExpr","FuncExpr","PropRef","ValueExpr"],"mappings":";;;;;;AAqDO,MAAM,iBAAqD;AAAA,EAGhE,YAAY,QAA0B,IAAI;AAF1C,SAAiB,QAA0B,CAAA;AAGzC,SAAK,QAAQ,EAAE,GAAG,MAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBACN,QACA,SACoC;AAGpC,QAAI;AACJ,QAAI;AACF,aAAO,OAAO,KAAK,MAAM;AAAA,IAC3B,QAAQ;AAEN,YAAM,OAAO,WAAW,OAAO,SAAS;AACxC,YAAM,IAAIA,OAAAA,uBAAuB,SAAS,IAAI;AAAA,IAChD;AAGA,QAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,YAAM,IAAIA,OAAAA,uBAAuB,SAAS,OAAO;AAAA,IACnD;AAGA,QAAI,KAAK,WAAW,GAAG;AACrB,UAAI,KAAK,WAAW,GAAG;AACrB,cAAM,IAAIA,OAAAA,uBAAuB,SAAS,cAAc;AAAA,MAC1D;AAEA,UAAI,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG;AACxC,cAAM,IAAIA,OAAAA,uBAAuB,SAAS,QAAQ;AAAA,MACpD;AACA,YAAM,IAAIC,OAAAA,0BAA0B,OAAO;AAAA,IAC7C;AAEA,UAAM,QAAQ,KAAK,CAAC;AACpB,UAAM,cAAc,OAAO,KAAK;AAGhC,QAAI;AAEJ,QAAI,uBAAuBC,MAAAA,gBAAgB;AACzC,YAAM,IAAIC,GAAAA,cAAc,aAAa,KAAK;AAAA,IAC5C,WAAW,uBAAuB,kBAAkB;AAClD,YAAM,WAAW,YAAY,UAAA;AAC7B,UAAI,CAAE,SAA8B,MAAM;AACxC,cAAM,IAAIC,OAAAA,gCAAgC,OAAO;AAAA,MACnD;AACA,YAAM,IAAIC,GAAAA,SAAS,UAAU,KAAK;AAAA,IACpC,OAAO;AACL,YAAM,IAAIC,OAAAA,mBAAmB,KAAK;AAAA,IACpC;AAEA,WAAO,CAAC,OAAO,GAAG;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,KACE,QAMC;AACD,UAAM,CAAA,EAAG,IAAI,IAAI,KAAK,oBAAoB,QAAQ,aAAa;AAE/D,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,KAIE,QACA,YAGA,OAAkB,QAGlB;AACA,UAAM,CAAC,OAAO,IAAI,IAAI,KAAK,oBAAoB,QAAQ,aAAa;AAGpE,UAAM,iBAAiB,KAAK,mBAAA;AAC5B,UAAM,aAAa,CAAC,GAAG,gBAAgB,KAAK;AAC5C,UAAMC,aAAWC,SAAAA,eAAe,UAAU;AAK1C,UAAM,eAAe,WAAWD,UAAQ;AAIxC,QAAI;AACJ,QAAI;AAEJ,QACE,aAAa,SAAS,UACtB,aAAa,SAAS,QACtB,aAAa,KAAK,WAAW,GAC7B;AACA,aAAO,aAAa,KAAK,CAAC;AAC1B,cAAQ,aAAa,KAAK,CAAC;AAAA,IAC7B,OAAO;AACL,YAAM,IAAIE,OAAAA,iCAAA;AAAA,IACZ;AAEA,UAAM,aAAyB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,gBAAgB,KAAK,MAAM,QAAQ,CAAA;AAEzC,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,MAAM,CAAC,GAAG,eAAe,UAAU;AAAA,IAAA,CACpC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,SACE,QACA,YAKA;AACA,WAAO,KAAK,KAAK,QAAQ,YAAY,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,UACE,QACA,YAKA;AACA,WAAO,KAAK,KAAK,QAAQ,YAAY,OAAO;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,UACE,QACA,YAKA;AACA,WAAO,KAAK,KAAK,QAAQ,YAAY,OAAO;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,SACE,QACA,YAKA;AACA,WAAO,KAAK,KAAK,QAAQ,YAAY,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,MAAM,UAA2D;AAC/D,UAAM,UAAU,KAAK,mBAAA;AACrB,UAAMF,aAAWC,SAAAA,eAAe,OAAO;AACvC,UAAM,aAAa,SAASD,UAAQ;AAKpC,QAAI,CAACG,GAAAA,iBAAiB,UAAU,GAAG;AACjC,YAAM,IAAIC,OAAAA,4BAA4B,iBAAiB,UAAU,CAAC;AAAA,IACpE;AAEA,UAAM,gBAAgB,KAAK,MAAM,SAAS,CAAA;AAE1C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,OAAO,CAAC,GAAG,eAAe,UAAU;AAAA,IAAA,CACrC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,OAAO,UAA2D;AAChE,UAAM,UAAU,KAAK,mBAAA;AAErB,UAAMJ,aACJ,KAAK,MAAM,UAAU,KAAK,MAAM,WAC5BK,SAAAA,2BAA2B,OAAO,IAClCJ,SAAAA,eAAe,OAAO;AAE5B,UAAM,aAAa,SAASD,UAAQ;AAKpC,QAAI,CAACG,GAAAA,iBAAiB,UAAU,GAAG;AACjC,YAAM,IAAIC,OAAAA,4BAA4B,iBAAiB,UAAU,CAAC;AAAA,IACpE;AAEA,UAAM,iBAAiB,KAAK,MAAM,UAAU,CAAA;AAE5C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,QAAQ,CAAC,GAAG,gBAAgB,UAAU;AAAA,IAAA,CACvC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoCA,OACE,UACyE;AACzE,UAAM,UAAU,KAAK,mBAAA;AACrB,UAAMJ,aAAWC,SAAAA,eAAe,OAAO;AACvC,UAAM,eAAe,SAASD,UAAQ;AACtC,UAAM,SAAS,kBAAkB,YAAY;AAE7C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR;AAAA,MACA,UAAU;AAAA;AAAA,IAAA,CACX;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,QACE,UACA,UAA6C,OACrB;AACxB,UAAM,UAAU,KAAK,mBAAA;AAErB,UAAMA,aACJ,KAAK,MAAM,UAAU,KAAK,MAAM,WAC5BK,SAAAA,2BAA2B,OAAO,IAClCJ,SAAAA,eAAe,OAAO;AAE5B,UAAM,SAAS,SAASD,UAAQ;AAEhC,UAAM,OACJ,OAAO,YAAY,WACf,EAAE,WAAW,SAAS,OAAO,QAAA,IAC7B;AAAA,MACE,WAAW,QAAQ,aAAa;AAAA,MAChC,OAAO,QAAQ,SAAS;AAAA,MACxB,YAAY,QAAQ;AAAA,MACpB,QACE,QAAQ,eAAe,WAAW,QAAQ,SAAS;AAAA,MACrD,eACE,QAAQ,eAAe,WACnB,QAAQ,gBACR;AAAA,IAAA;AAGd,UAAM,oBAAoB,CAAC,QAAa;AACtC,aAAO;AAAA,QACL,YAAYM,SAAAA,aAAa,GAAG;AAAA,QAC5B,gBAAgB;AAAA,MAAA;AAAA,IAEpB;AAGA,UAAM,iBAAiB,MAAM,QAAQ,MAAM,IACvC,OAAO,IAAI,CAAC,MAAM,kBAAkB,CAAC,CAAC,IACtC,CAAC,kBAAkB,MAAM,CAAC;AAE9B,UAAM,kBAA2B,KAAK,MAAM,WAAW,CAAA;AAEvD,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,SAAS,CAAC,GAAG,iBAAiB,GAAG,cAAc;AAAA,IAAA,CAChD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,QAAQ,UAA6D;AACnE,UAAM,UAAU,KAAK,mBAAA;AACrB,UAAMN,aAAWC,SAAAA,eAAe,OAAO;AACvC,UAAM,SAAS,SAASD,UAAQ;AAEhC,UAAM,iBAAiB,MAAM,QAAQ,MAAM,IACvC,OAAO,IAAI,CAAC,MAAMM,SAAAA,aAAa,CAAC,CAAC,IACjC,CAACA,SAAAA,aAAa,MAAM,CAAC;AAGzB,UAAM,kBAAkB,KAAK,MAAM,WAAW,CAAA;AAC9C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,SAAS,CAAC,GAAG,iBAAiB,GAAG,cAAc;AAAA,IAAA,CAChD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,OAAuC;AAC3C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,OAAO;AAAA,IAAA,CACR;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,OAAO,OAAuC;AAC5C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,QAAQ;AAAA,IAAA,CACT;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,WAAmC;AACjC,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA,MACR,UAAU;AAAA,IAAA,CACX;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,UAAiD;AAC/C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,GAAG,KAAK;AAAA;AAAA;AAAA,MAGR,cAAc;AAAA,IAAA,CACf;AAAA,EACH;AAAA;AAAA,EAGQ,qBAAoC;AAC1C,UAAM,UAAyB,CAAA;AAG/B,QAAI,KAAK,MAAM,MAAM;AACnB,cAAQ,KAAK,KAAK,MAAM,KAAK,KAAK;AAAA,IACpC;AAGA,QAAI,KAAK,MAAM,MAAM;AACnB,iBAAW,QAAQ,KAAK,MAAM,MAAM;AAClC,gBAAQ,KAAK,KAAK,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,IAAI,KAAK;AACP,UAAM,UAAU;AAChB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAmBL,OACE,UACuD;AACvD,eAAO,IAAI,iBAAiB;AAAA,UAC1B,GAAG,QAAQ;AAAA,UACX,QAAQ;AAAA;AAAA,UACR,UAAU;AAAA,QAAA,CACX;AAAA,MACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgBA,MACE,UACwB;AACxB,eAAO,IAAI,iBAAiB;AAAA,UAC1B,GAAG,QAAQ;AAAA,UACX,SAAS;AAAA,YACP,GAAI,QAAQ,MAAM,WAAW,CAAA;AAAA,YAC7B;AAAA,UAAA;AAAA,QACF,CACD;AAAA,MACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAkBA,OACE,UACwB;AACxB,eAAO,IAAI,iBAAiB;AAAA,UAC1B,GAAG,QAAQ;AAAA,UACX,UAAU;AAAA,YACR,GAAI,QAAQ,MAAM,YAAY,CAAA;AAAA,YAC9B;AAAA,UAAA;AAAA,QACF,CACD;AAAA,MACH;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,YAAqB;AACnB,QAAI,CAAC,KAAK,MAAM,MAAM;AACpB,YAAM,IAAIC,OAAAA,6BAAA;AAAA,IACZ;AACA,WAAO,KAAK;AAAA,EACd;AACF;AAGA,SAAS,iBAAiB,OAAwB;AAChD,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,OAAO;AAChB;AAGA,SAAS,OAAO,OAAyC;AACvD,MAAI,UAAU,OAAW,QAAOD,SAAAA,aAAa,IAAI;AACjD,MACE,iBAAiBE,GAAAA,aACjB,iBAAiBC,GAAAA,QACjB,iBAAiBC,GAAAA,WACjB,iBAAiBC,UACjB;AACA,WAAO;AAAA,EACT;AACA,SAAOL,SAAAA,aAAa,KAAK;AAC3B;AAEA,SAAS,cAAc,OAA0C;AAC/D,SACE,UAAU,QACV,OAAO,UAAU,YACjB,CAACH,oBAAiB,KAAK,KACvB,CAAC,MAAM;AAEX;AAEA,SAAS,kBAAkB,KAAe;AACxC,MAAI,CAAC,cAAc,GAAG,EAAG,QAAO,OAAO,GAAG;AAC1C,QAAM,MAA2B,CAAA;AACjC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,OAAO,MAAM,YAAY,EAAE,WAAW,qBAAqB,GAAG;AAEhE,UAAI,CAAC,IAAI;AACT;AAAA,IACF;AACA,QAAI,CAAC,IAAI,kBAAkB,CAAC;AAAA,EAC9B;AACA,SAAO;AACT;AAIO,SAAS,WACd,IACS;AACT,QAAM,SAAS,GAAG,IAAI,kBAAkB;AACxC,SAAO,WAAW,MAAM;AAC1B;AAGO,SAAS,WACd,SACS;AACT,SAAQ,QAAwC,UAAA;AAClD;AAcO,MAAM,QAAwC;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from '../../collection/index.js'\nimport {\n Aggregate as AggregateExpr,\n CollectionRef,\n Func as FuncExpr,\n PropRef,\n QueryRef,\n Value as ValueExpr,\n isExpressionLike,\n} from '../ir.js'\nimport {\n InvalidSourceError,\n InvalidSourceTypeError,\n InvalidWhereExpressionError,\n JoinConditionMustBeEqualityError,\n OnlyOneSourceAllowedError,\n QueryMustHaveFromClauseError,\n SubQueryMustHaveFromClauseError,\n} from '../../errors.js'\nimport {\n createRefProxy,\n createRefProxyWithSelected,\n toExpression,\n} from './ref-proxy.js'\nimport type { NamespacedRow, SingleResult } from '../../types.js'\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByDirection,\n QueryIR,\n} from '../ir.js'\nimport type {\n CompareOptions,\n Context,\n FunctionalHavingRow,\n GetResult,\n GroupByCallback,\n JoinOnCallback,\n MergeContextForJoinCallback,\n MergeContextWithJoinType,\n OrderByCallback,\n OrderByOptions,\n RefsForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from './types.js'\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string,\n ): [string, CollectionRef | QueryRef] {\n // Validate source is a plain object (not null, array, string, etc.)\n // We use try-catch to handle null/undefined gracefully\n let keys: Array<string>\n try {\n keys = Object.keys(source)\n } catch {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const type = source === null ? `null` : `undefined`\n throw new InvalidSourceTypeError(context, type)\n }\n\n // Check if it's an array (arrays pass Object.keys but aren't valid sources)\n if (Array.isArray(source)) {\n throw new InvalidSourceTypeError(context, `array`)\n }\n\n // Validate exactly one key\n if (keys.length !== 1) {\n if (keys.length === 0) {\n throw new InvalidSourceTypeError(context, `empty object`)\n }\n // Check if it looks like a string was passed (has numeric keys)\n if (keys.every((k) => !isNaN(Number(k)))) {\n throw new InvalidSourceTypeError(context, `string`)\n }\n throw new OnlyOneSourceAllowedError(context)\n }\n\n const alias = keys[0]!\n const sourceValue = source[alias]\n\n // Validate the value is a Collection or QueryBuilder\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new SubQueryMustHaveFromClauseError(context)\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new InvalidSourceError(alias)\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource,\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefsForContext<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new JoinConditionMustBeEqualityError()\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Perform a LEFT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the left joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .leftJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n leftJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `left`>\n > {\n return this.join(source, onCallback, `left`)\n }\n\n /**\n * Perform a RIGHT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the right joined table available\n *\n * @example\n * ```ts\n * // Right join users with posts\n * query\n * .from({ users: usersCollection })\n * .rightJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n rightJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `right`>\n > {\n return this.join(source, onCallback, `right`)\n }\n\n /**\n * Perform an INNER JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the inner joined table available\n *\n * @example\n * ```ts\n * // Inner join users with posts\n * query\n * .from({ users: usersCollection })\n * .innerJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n innerJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `inner`>\n > {\n return this.join(source, onCallback, `inner`)\n }\n\n /**\n * Perform a FULL JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the full joined table available\n *\n * @example\n * ```ts\n * // Full join users with posts\n * query\n * .from({ users: usersCollection })\n * .fullJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n fullJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>\n >,\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `full`>\n > {\n return this.join(source, onCallback, `full`)\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const expression = callback(refProxy)\n\n // Validate that the callback returned a valid expression\n // This catches common mistakes like using JavaScript comparison operators (===, !==, etc.)\n // which return boolean primitives instead of expression objects\n if (!isExpressionLike(expression)) {\n throw new InvalidWhereExpressionError(getValueTypeName(expression))\n }\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n // Add $selected namespace if SELECT clause exists (either regular or functional)\n const refProxy = (\n this.query.select || this.query.fnSelect\n ? createRefProxyWithSelected(aliases)\n : createRefProxy(aliases)\n ) as RefsForContext<TContext>\n const expression = callback(refProxy)\n\n // Validate that the callback returned a valid expression\n // This catches common mistakes like using JavaScript comparison operators (===, !==, etc.)\n // which return boolean primitives instead of expression objects\n if (!isExpressionLike(expression)) {\n throw new InvalidWhereExpressionError(getValueTypeName(expression))\n }\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefsForContext<TContext>) => TSelectObject,\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const selectObject = callback(refProxy)\n const select = buildNestedSelect(selectObject)\n\n return new BaseQueryBuilder({\n ...this.query,\n select: select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n options: OrderByDirection | OrderByOptions = `asc`,\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n // Add $selected namespace if SELECT clause exists (either regular or functional)\n const refProxy = (\n this.query.select || this.query.fnSelect\n ? createRefProxyWithSelected(aliases)\n : createRefProxy(aliases)\n ) as RefsForContext<TContext>\n const result = callback(refProxy)\n\n const opts: CompareOptions =\n typeof options === `string`\n ? { direction: options, nulls: `first` }\n : {\n direction: options.direction ?? `asc`,\n nulls: options.nulls ?? `first`,\n stringSort: options.stringSort,\n locale:\n options.stringSort === `locale` ? options.locale : undefined,\n localeOptions:\n options.stringSort === `locale`\n ? options.localeOptions\n : undefined,\n }\n\n const makeOrderByClause = (res: any) => {\n return {\n expression: toExpression(res),\n compareOptions: opts,\n }\n }\n\n // Create the new OrderBy structure with expression and direction\n const orderByClauses = Array.isArray(result)\n ? result.map((r) => makeOrderByClause(r))\n : [makeOrderByClause(result)]\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, ...orderByClauses],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefsForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Extend existing groupBy expressions (multiple groupBy calls should accumulate)\n const existingGroupBy = this.query.groupBy || []\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: [...existingGroupBy, ...newExpressions],\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n /**\n * Specify that the query should return distinct rows.\n * Deduplicates rows based on the selected columns.\n * @returns A QueryBuilder with distinct enabled\n *\n * @example\n * ```ts\n * // Get countries our users are from\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => users.country)\n * .distinct()\n * ```\n */\n distinct(): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n distinct: true,\n }) as any\n }\n\n /**\n * Specify that the query should return a single result\n * @returns A QueryBuilder that returns the first result\n *\n * @example\n * ```ts\n * // Get the user matching the query\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => eq(users.id, 1))\n * .findOne()\n *```\n */\n findOne(): QueryBuilder<TContext & SingleResult> {\n return new BaseQueryBuilder({\n ...this.query,\n // TODO: enforcing return only one result with also a default orderBy if none is specified\n // limit: 1,\n singleResult: true,\n })\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult,\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any,\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row (with $selected when select() was called) and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts}) => ({ userId: posts.userId, count: count(posts.id) }))\n * .fn.having(({ $selected }) => $selected.count > 5)\n * ```\n */\n having(\n callback: (row: FunctionalHavingRow<TContext>) => any,\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new QueryMustHaveFromClauseError()\n }\n return this.query as QueryIR\n }\n}\n\n// Helper to get a descriptive type name for error messages\nfunction getValueTypeName(value: unknown): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value === `object`) return `object`\n return typeof value\n}\n\n// Helper to ensure we have a BasicExpression/Aggregate for a value\nfunction toExpr(value: any): BasicExpression | Aggregate {\n if (value === undefined) return toExpression(null)\n if (\n value instanceof AggregateExpr ||\n value instanceof FuncExpr ||\n value instanceof PropRef ||\n value instanceof ValueExpr\n ) {\n return value as BasicExpression | Aggregate\n }\n return toExpression(value)\n}\n\nfunction isPlainObject(value: any): value is Record<string, any> {\n return (\n value !== null &&\n typeof value === `object` &&\n !isExpressionLike(value) &&\n !value.__refProxy\n )\n}\n\nfunction buildNestedSelect(obj: any): any {\n if (!isPlainObject(obj)) return toExpr(obj)\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (typeof k === `string` && k.startsWith(`__SPREAD_SENTINEL__`)) {\n // Preserve sentinel key and its value (value is unimportant at compile time)\n out[k] = v\n continue\n }\n out[k] = buildNestedSelect(v)\n }\n return out\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>,\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder,\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Helper type to extract the result type from a QueryBuilder (similar to Zod's z.infer)\nexport type QueryResult<T> = GetResult<ExtractContext<T>>\n\n// Export the types from types.ts for convenience\nexport type {\n Context,\n ContextSchema,\n Source,\n GetResult,\n RefLeaf as Ref,\n InferResultType,\n // Types used in public method signatures that must be exported\n // for declaration emit to work (see https://github.com/TanStack/db/issues/1012)\n SchemaFromSource,\n InferCollectionType,\n MergeContextWithJoinType,\n MergeContextForJoinCallback,\n ApplyJoinOptionalityToMergedSchema,\n ResultTypeFromSelect,\n WithResult,\n JoinOnCallback,\n RefsForContext,\n WhereCallback,\n OrderByCallback,\n GroupByCallback,\n SelectObject,\n FunctionalHavingRow,\n Prettify,\n} from './types.js'\n"],"names":["InvalidSourceTypeError","OnlyOneSourceAllowedError","CollectionImpl","CollectionRef","SubQueryMustHaveFromClauseError","QueryRef","InvalidSourceError","refProxy","createRefProxy","JoinConditionMustBeEqualityError","isExpressionLike","InvalidWhereExpressionError","createRefProxyWithSelected","toExpression","QueryMustHaveFromClauseError","AggregateExpr","FuncExpr","PropRef","ValueExpr"],"mappings":"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"}
@@ -417,4 +417,4 @@ export type QueryBuilder<TContext extends Context> = Omit<BaseQueryBuilder<TCont
417
417
  export declare const Query: InitialQueryBuilderConstructor;
418
418
  export type ExtractContext<T> = T extends BaseQueryBuilder<infer TContext> ? TContext : T extends QueryBuilder<infer TContext> ? TContext : never;
419
419
  export type QueryResult<T> = GetResult<ExtractContext<T>>;
420
- export type { Context, Source, GetResult, RefLeaf as Ref, InferResultType, } from './types.js';
420
+ export type { Context, ContextSchema, Source, GetResult, RefLeaf as Ref, InferResultType, SchemaFromSource, InferCollectionType, MergeContextWithJoinType, MergeContextForJoinCallback, ApplyJoinOptionalityToMergedSchema, ResultTypeFromSelect, WithResult, JoinOnCallback, RefsForContext, WhereCallback, OrderByCallback, GroupByCallback, SelectObject, FunctionalHavingRow, Prettify, } from './types.js';
@@ -218,7 +218,7 @@ function compileFunction(func, isSingleRow) {
218
218
  const valueEvaluator = compiledArgs[0];
219
219
  const arrayEvaluator = compiledArgs[1];
220
220
  return (data) => {
221
- const value = valueEvaluator(data);
221
+ const value = comparison.normalizeValue(valueEvaluator(data));
222
222
  const array = arrayEvaluator(data);
223
223
  if (isUnknown(value)) {
224
224
  return null;
@@ -226,7 +226,7 @@ function compileFunction(func, isSingleRow) {
226
226
  if (!Array.isArray(array)) {
227
227
  return false;
228
228
  }
229
- return array.includes(value);
229
+ return array.some((item) => comparison.normalizeValue(item) === value);
230
230
  };
231
231
  }
232
232
  // String operators
@@ -1 +1 @@
1
- {"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import {\n EmptyReferencePathError,\n UnknownExpressionTypeError,\n UnknownFunctionError,\n} from '../../errors.js'\nimport { areValuesEqual, normalizeValue } from '../../utils/comparison.js'\nimport type { BasicExpression, Func, PropRef } from '../ir.js'\nimport type { NamespacedRow } from '../../types.js'\n\n/**\n * Helper function to check if a value is null or undefined (represents UNKNOWN in 3-valued logic)\n */\nfunction isUnknown(value: any): boolean {\n return value === null || value === undefined\n}\n\n/**\n * Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters.\n * In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior.\n *\n * @param result - The 3-valued logic result: true, false, or null (UNKNOWN)\n * @returns true only if result is explicitly true, false otherwise\n *\n * Truth table:\n * - true → true (include row)\n * - false → false (exclude row)\n * - null (UNKNOWN) → false (exclude row, matching SQL behavior)\n */\nexport function toBooleanPredicate(result: boolean | null): boolean {\n return result === true\n}\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiled single-row expression evaluator function type\n */\nexport type CompiledSingleRowExpression = (item: Record<string, unknown>) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(\n expr: BasicExpression,\n isSingleRow: boolean = false,\n): CompiledExpression | CompiledSingleRowExpression {\n const compiledFn = compileExpressionInternal(expr, isSingleRow)\n return compiledFn\n}\n\n/**\n * Compiles a single-row expression into an optimized evaluator function.\n */\nexport function compileSingleRowExpression(\n expr: BasicExpression,\n): CompiledSingleRowExpression {\n const compiledFn = compileExpressionInternal(expr, true)\n return compiledFn as CompiledSingleRowExpression\n}\n\n/**\n * Internal unified expression compiler that handles both namespaced and single-row evaluation\n */\nfunction compileExpressionInternal(\n expr: BasicExpression,\n isSingleRow: boolean,\n): (data: any) => any {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, compile based on evaluation mode\n return isSingleRow ? compileSingleRowRef(expr) : compileRef(expr)\n }\n\n case `func`: {\n // For functions, use the unified compiler\n return compileFunction(expr, isSingleRow)\n }\n\n default:\n throw new UnknownExpressionTypeError((expr as any).type)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: PropRef): CompiledExpression {\n const [namespace, ...propertyPath] = ref.path\n\n if (!namespace) {\n throw new EmptyReferencePathError()\n }\n\n // Handle $selected namespace - references SELECT result fields\n if (namespace === `$selected`) {\n // Access $selected directly\n if (propertyPath.length === 0) {\n // Just $selected - return entire $selected object\n return (namespacedRow) => (namespacedRow as any).$selected\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const selectResults = (namespacedRow as any).$selected\n return selectResults?.[prop]\n }\n } else {\n // Multiple property navigation (nested SELECT fields)\n return (namespacedRow) => {\n const selectResults = (namespacedRow as any).$selected\n if (selectResults === undefined) {\n return undefined\n }\n\n let value: any = selectResults\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n }\n\n // Handle table alias namespace (existing logic)\n const tableAlias = namespace\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a reference expression for single-row evaluation\n */\nfunction compileSingleRowRef(ref: PropRef): CompiledSingleRowExpression {\n const propertyPath = ref.path\n\n // This function works for all path lengths including empty path\n return (item) => {\n let value: any = item\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n}\n\n/**\n * Compiles a function expression for both namespaced and single-row evaluation\n */\nfunction compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {\n // Pre-compile all arguments using the appropriate compiler\n const compiledArgs = func.args.map((arg) =>\n compileExpressionInternal(arg, isSingleRow),\n )\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = normalizeValue(argA(data))\n const b = normalizeValue(argB(data))\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n // Use areValuesEqual for proper Uint8Array/Buffer comparison\n return areValuesEqual(a, b)\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (data) => {\n // 3-valued logic for AND:\n // - false AND anything = false (short-circuit)\n // - null AND false = false\n // - null AND anything (except false) = null\n // - anything (except false) AND null = null\n // - true AND true = true\n let hasUnknown = false\n for (const compiledArg of compiledArgs) {\n const result = compiledArg(data)\n if (result === false) {\n return false\n }\n if (isUnknown(result)) {\n hasUnknown = true\n }\n }\n // If we got here, no operand was false\n // If any operand was null, return null (UNKNOWN)\n if (hasUnknown) {\n return null\n }\n\n return true\n }\n case `or`:\n return (data) => {\n // 3-valued logic for OR:\n // - true OR anything = true (short-circuit)\n // - null OR anything (except true) = null\n // - false OR false = false\n let hasUnknown = false\n for (const compiledArg of compiledArgs) {\n const result = compiledArg(data)\n if (result === true) {\n return true\n }\n if (isUnknown(result)) {\n hasUnknown = true\n }\n }\n // If we got here, no operand was true\n // If any operand was null, return null (UNKNOWN)\n if (hasUnknown) {\n return null\n }\n\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (data) => {\n // 3-valued logic for NOT:\n // - NOT null = null\n // - NOT true = false\n // - NOT false = true\n const result = arg(data)\n if (isUnknown(result)) {\n return null\n }\n return !result\n }\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (data) => {\n const value = valueEvaluator(data)\n const array = arrayEvaluator(data)\n // In 3-valued logic, if the value is null/undefined, return UNKNOWN\n if (isUnknown(value)) {\n return null\n }\n if (!Array.isArray(array)) {\n return false\n }\n return array.includes(value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (data) => {\n const value = valueEvaluator(data)\n const pattern = patternEvaluator(data)\n // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN\n if (isUnknown(value) || isUnknown(pattern)) {\n return null\n }\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (data) => {\n const value = valueEvaluator(data)\n const pattern = patternEvaluator(data)\n // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN\n if (isUnknown(value) || isUnknown(pattern)) {\n return null\n }\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (data) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(data)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (data) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(data)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n // Null/undefined checking functions\n case `isUndefined`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return value === undefined\n }\n }\n case `isNull`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return value === null\n }\n }\n\n default:\n throw new UnknownFunctionError(func.name)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean,\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":["UnknownExpressionTypeError","EmptyReferencePathError","normalizeValue","areValuesEqual","UnknownFunctionError"],"mappings":"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"}
1
+ {"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import {\n EmptyReferencePathError,\n UnknownExpressionTypeError,\n UnknownFunctionError,\n} from '../../errors.js'\nimport { areValuesEqual, normalizeValue } from '../../utils/comparison.js'\nimport type { BasicExpression, Func, PropRef } from '../ir.js'\nimport type { NamespacedRow } from '../../types.js'\n\n/**\n * Helper function to check if a value is null or undefined (represents UNKNOWN in 3-valued logic)\n */\nfunction isUnknown(value: any): boolean {\n return value === null || value === undefined\n}\n\n/**\n * Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters.\n * In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior.\n *\n * @param result - The 3-valued logic result: true, false, or null (UNKNOWN)\n * @returns true only if result is explicitly true, false otherwise\n *\n * Truth table:\n * - true → true (include row)\n * - false → false (exclude row)\n * - null (UNKNOWN) → false (exclude row, matching SQL behavior)\n */\nexport function toBooleanPredicate(result: boolean | null): boolean {\n return result === true\n}\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiled single-row expression evaluator function type\n */\nexport type CompiledSingleRowExpression = (item: Record<string, unknown>) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(\n expr: BasicExpression,\n isSingleRow: boolean = false,\n): CompiledExpression | CompiledSingleRowExpression {\n const compiledFn = compileExpressionInternal(expr, isSingleRow)\n return compiledFn\n}\n\n/**\n * Compiles a single-row expression into an optimized evaluator function.\n */\nexport function compileSingleRowExpression(\n expr: BasicExpression,\n): CompiledSingleRowExpression {\n const compiledFn = compileExpressionInternal(expr, true)\n return compiledFn as CompiledSingleRowExpression\n}\n\n/**\n * Internal unified expression compiler that handles both namespaced and single-row evaluation\n */\nfunction compileExpressionInternal(\n expr: BasicExpression,\n isSingleRow: boolean,\n): (data: any) => any {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, compile based on evaluation mode\n return isSingleRow ? compileSingleRowRef(expr) : compileRef(expr)\n }\n\n case `func`: {\n // For functions, use the unified compiler\n return compileFunction(expr, isSingleRow)\n }\n\n default:\n throw new UnknownExpressionTypeError((expr as any).type)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: PropRef): CompiledExpression {\n const [namespace, ...propertyPath] = ref.path\n\n if (!namespace) {\n throw new EmptyReferencePathError()\n }\n\n // Handle $selected namespace - references SELECT result fields\n if (namespace === `$selected`) {\n // Access $selected directly\n if (propertyPath.length === 0) {\n // Just $selected - return entire $selected object\n return (namespacedRow) => (namespacedRow as any).$selected\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const selectResults = (namespacedRow as any).$selected\n return selectResults?.[prop]\n }\n } else {\n // Multiple property navigation (nested SELECT fields)\n return (namespacedRow) => {\n const selectResults = (namespacedRow as any).$selected\n if (selectResults === undefined) {\n return undefined\n }\n\n let value: any = selectResults\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n }\n\n // Handle table alias namespace (existing logic)\n const tableAlias = namespace\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a reference expression for single-row evaluation\n */\nfunction compileSingleRowRef(ref: PropRef): CompiledSingleRowExpression {\n const propertyPath = ref.path\n\n // This function works for all path lengths including empty path\n return (item) => {\n let value: any = item\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n}\n\n/**\n * Compiles a function expression for both namespaced and single-row evaluation\n */\nfunction compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {\n // Pre-compile all arguments using the appropriate compiler\n const compiledArgs = func.args.map((arg) =>\n compileExpressionInternal(arg, isSingleRow),\n )\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = normalizeValue(argA(data))\n const b = normalizeValue(argB(data))\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n // Use areValuesEqual for proper Uint8Array/Buffer comparison\n return areValuesEqual(a, b)\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n // In 3-valued logic, any comparison with null/undefined returns UNKNOWN\n if (isUnknown(a) || isUnknown(b)) {\n return null\n }\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (data) => {\n // 3-valued logic for AND:\n // - false AND anything = false (short-circuit)\n // - null AND false = false\n // - null AND anything (except false) = null\n // - anything (except false) AND null = null\n // - true AND true = true\n let hasUnknown = false\n for (const compiledArg of compiledArgs) {\n const result = compiledArg(data)\n if (result === false) {\n return false\n }\n if (isUnknown(result)) {\n hasUnknown = true\n }\n }\n // If we got here, no operand was false\n // If any operand was null, return null (UNKNOWN)\n if (hasUnknown) {\n return null\n }\n\n return true\n }\n case `or`:\n return (data) => {\n // 3-valued logic for OR:\n // - true OR anything = true (short-circuit)\n // - null OR anything (except true) = null\n // - false OR false = false\n let hasUnknown = false\n for (const compiledArg of compiledArgs) {\n const result = compiledArg(data)\n if (result === true) {\n return true\n }\n if (isUnknown(result)) {\n hasUnknown = true\n }\n }\n // If we got here, no operand was true\n // If any operand was null, return null (UNKNOWN)\n if (hasUnknown) {\n return null\n }\n\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (data) => {\n // 3-valued logic for NOT:\n // - NOT null = null\n // - NOT true = false\n // - NOT false = true\n const result = arg(data)\n if (isUnknown(result)) {\n return null\n }\n return !result\n }\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (data) => {\n const value = normalizeValue(valueEvaluator(data))\n const array = arrayEvaluator(data)\n // In 3-valued logic, if the value is null/undefined, return UNKNOWN\n if (isUnknown(value)) {\n return null\n }\n if (!Array.isArray(array)) {\n return false\n }\n return array.some((item) => normalizeValue(item) === value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (data) => {\n const value = valueEvaluator(data)\n const pattern = patternEvaluator(data)\n // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN\n if (isUnknown(value) || isUnknown(pattern)) {\n return null\n }\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (data) => {\n const value = valueEvaluator(data)\n const pattern = patternEvaluator(data)\n // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN\n if (isUnknown(value) || isUnknown(pattern)) {\n return null\n }\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (data) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(data)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (data) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(data)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (data) => {\n const a = argA(data)\n const b = argB(data)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n // Null/undefined checking functions\n case `isUndefined`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return value === undefined\n }\n }\n case `isNull`: {\n const arg = compiledArgs[0]!\n return (data) => {\n const value = arg(data)\n return value === null\n }\n }\n\n default:\n throw new UnknownFunctionError(func.name)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean,\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":["UnknownExpressionTypeError","EmptyReferencePathError","normalizeValue","areValuesEqual","UnknownFunctionError"],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const dbIvm = require("@tanstack/db-ivm");
4
4
  const errors = require("../../errors.cjs");
5
+ const comparison = require("../../utils/comparison.cjs");
5
6
  const autoIndex = require("../../indexes/auto-index.cjs");
6
7
  const ir = require("../ir.cjs");
7
8
  const functions = require("../builder/functions.cjs");
@@ -83,14 +84,14 @@ function processJoin(pipeline, joinClause, sources, mainCollectionId, mainSource
83
84
  const compiledJoinedExpr = evaluators.compileExpression(joinedExpr);
84
85
  let mainPipeline = pipeline.pipe(
85
86
  dbIvm.map(([currentKey, namespacedRow]) => {
86
- const mainKey = compiledMainExpr(namespacedRow);
87
+ const mainKey = comparison.normalizeValue(compiledMainExpr(namespacedRow));
87
88
  return [mainKey, [currentKey, namespacedRow]];
88
89
  })
89
90
  );
90
91
  let joinedPipeline = joinedInput.pipe(
91
92
  dbIvm.map(([currentKey, row]) => {
92
93
  const namespacedRow = { [joinedSource]: row };
93
- const joinedKey = compiledJoinedExpr(namespacedRow);
94
+ const joinedKey = comparison.normalizeValue(compiledJoinedExpr(namespacedRow));
94
95
  return [joinedKey, [currentKey, namespacedRow]];
95
96
  })
96
97
  );
@@ -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 { 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 = 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 = 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","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 // 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":";;;;;;;;;AAkDO,SAAS,aACd,UACA,aACA,SACA,kBACA,YACA,WACA,OACA,cACA,aACA,eACA,WACA,aACA,+BACA,aACA,UACA,mBACA,qBACA,gBACA,oBAC0B;AAC1B,MAAI,iBAAiB;AAErB,aAAW,cAAc,aAAa;AACpC,qBAAiB;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AACT;AAMA,SAAS,YACP,UACA,YACA,SACA,kBACA,YACA,WACA,OACA,cACA,aACA,eACA,WACA,aACA,+BACA,aACA,UACA,mBACA,qBACA,gBACA,oBAC0B;AAC1B,QAAM,kBAAkB,WAAW,KAAK,SAAS;AAGjD,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,cAAc;AAAA,EAAA,IACZ;AAAA,IACF,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAIF,UAAQ,YAAY,IAAI;AACxB,MAAI,iBAAiB;AAGnB,wBAAoB,YAAY,IAAI;AAAA,EACtC;AAEA,QAAM,iBAAiB,YAAY,gBAAgB;AACnD,QAAM,mBAAmB,YAAY,kBAAkB;AAEvD,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAIA,OAAAA,4BAA4B,gBAAgB;AAAA,EACxD;AAEA,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAIA,OAAAA,4BAA4B,kBAAkB;AAAA,EAC1D;AAEA,QAAM,EAAE,cAAc,WAAA,IAAe;AAAA,IACnC,WAAW;AAAA,IACX;AAAA,IACA;AAAA,EAAA;AAIF,QAAM,mBAAmB,OAAO,KAAK,OAAO;AAC5C,QAAM,EAAE,UAAU,WAAA,IAAe;AAAA,IAC/B,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,IACA;AAAA,EAAA;AAIF,QAAM,mBAAmBC,WAAAA,kBAAkB,QAAQ;AACnD,QAAM,qBAAqBA,WAAAA,kBAAkB,UAAU;AAGvD,MAAI,eAAe,SAAS;AAAA,IAC1BC,MAAAA,IAAI,CAAC,CAAC,YAAY,aAAa,MAAM;AAEnC,YAAM,UAAUC,WAAAA,eAAe,iBAAiB,aAAa,CAAC;AAG9D,aAAO,CAAC,SAAS,CAAC,YAAY,aAAa,CAAC;AAAA,IAI9C,CAAC;AAAA,EAAA;AAIH,MAAI,iBAAiB,YAAY;AAAA,IAC/BD,MAAAA,IAAI,CAAC,CAAC,YAAY,GAAG,MAAM;AAEzB,YAAM,gBAA+B,EAAE,CAAC,YAAY,GAAG,IAAA;AAGvD,YAAM,YAAYC,WAAAA,eAAe,mBAAmB,aAAa,CAAC;AAGlE,aAAO,CAAC,WAAW,CAAC,YAAY,aAAa,CAAC;AAAA,IAIhD,CAAC;AAAA,EAAA;AAIH,MAAI,CAAC,CAAC,SAAS,QAAQ,SAAS,MAAM,EAAE,SAAS,WAAW,IAAI,GAAG;AACjE,UAAM,IAAIC,OAAAA,yBAAyB,WAAW,IAAI;AAAA,EACpD;AAEA,MAAI,cAAc;AAKhB,UAAM,WAAW,iBAAiB,SAAS,WAAW,OAAO,SAAS;AACtE,UAAM,kBACJ,SAAS,SAAS,eACjB,SAAS,MAAM,SAAS,SAAS,MAAM;AAI1C,UAAM,sBACJ,SAAS,SAAS,UAAU,WAAW,SAAS;AAElD,QAAI,CAAC,mBAAmB,CAAC,qBAAqB;AAU5C,YAAM,YAAY,iBAAiB,SAAS,eAAe;AAC3D,kBAAY,IAAI,SAAS;AAEzB,YAAM,iBACJ,iBAAiB,SAAS,eAAe;AAE3C,YAAM,qBACJ,iBAAiB,SACZ,aACA;AAEP,YAAM,kBAAkBC,GAAAA;AAAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAEF,YAAM,sBAAsB,gBAAgB;AAE5C,YAAM,YAAY,gBAAgB,KAAK,CAAC;AACxC,UAAI,WAAW;AACbC,kBAAAA;AAAAA,UACE;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,MAEJ;AAIA,YAAM,4BAEF,eAAe;AAAA,QACjBC,MAAAA,IAAI,CAAC,SAAS;AAOZ,gBAAM,gBAAgB,eAAe,SAAS,KAAK;AACnD,gBAAM,yBAAyB,cAAc,aAAa;AAE1D,cAAI,CAAC,wBAAwB;AAC3B,kBAAM,IAAIC,OAAAA;AAAAA,cACR;AAAA,cACA;AAAA,cACA,WAAW;AAAA,cACX,OAAO,KAAK,aAAa;AAAA,YAAA;AAAA,UAE7B;AAEA,cAAI,uBAAuB,yBAAyB;AAElD;AAAA,UACF;AAGA,gBAAM,WAAW,KAAK,WAAW,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,OAAO;AAC7D,gBAAM,cAAc,IAAIC,WAAQ,gBAAgB,IAAI;AACpD,gBAAM,SAAS,uBAAuB,gBAAgB;AAAA,YACpD,OAAOC,UAAAA,QAAQ,aAAa,QAAQ;AAAA,YACpC,eAAe;AAAA,UAAA,CAChB;AAED,cAAI,CAAC,QAAQ;AAEX,mCAAuB,gBAAA;AAAA,UACzB;AAAA,QACF,CAAC;AAAA,MAAA;AAGH,UAAI,iBAAiB,QAAQ;AAC3B,uBAAe;AAAA,MACjB,OAAO;AACL,yBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IAClBC,WAAa,gBAAgB,WAAW,IAAgB;AAAA,IACxD,mBAAmB,WAAW,IAAI;AAAA,EAAA;AAEtC;AAMA,SAAS,uBACP,MACA,OACA,2BACA,cAC4D;AAE5D,QAAM,mBAAmB,0BAA0B;AAAA,IACjD,CAAC,UAAU,UAAU;AAAA,EAAA;AAGvB,QAAM,kBAAkB,6BAA6B,IAAI;AACzD,QAAM,mBAAmB,6BAA6B,KAAK;AAG3D,MACE,mBACA,iBAAiB,SAAS,eAAe,KACzC,qBAAqB,cACrB;AACA,WAAO,EAAE,UAAU,MAAM,YAAY,MAAA;AAAA,EACvC;AAGA,MACE,oBAAoB,gBACpB,oBACA,iBAAiB,SAAS,gBAAgB,GAC1C;AACA,WAAO,EAAE,UAAU,OAAO,YAAY,KAAA;AAAA,EACxC;AAGA,MAAI,CAAC,mBAAmB,CAAC,kBAAkB;AACzC,UAAM,IAAIC,OAAAA,wCAAA;AAAA,EACZ;AAGA,MAAI,oBAAoB,kBAAkB;AACxC,UAAM,IAAIC,OAAAA,oCAAoC,eAAe;AAAA,EAC/D;AAKA,MAAI,CAAC,iBAAiB,SAAS,eAAe,GAAG;AAC/C,UAAM,IAAIC,OAAAA,oCAAoC,eAAe;AAAA,EAC/D;AAGA,MAAI,qBAAqB,cAAc;AACrC,UAAM,IAAIC,OAAAA,qCAAqC,YAAY;AAAA,EAC7D;AAGA,QAAM,IAAIC,OAAAA,qBAAA;AACZ;AAKA,SAAS,6BAA6B,MAAsC;AAC1E,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK;AAEH,aAAO,KAAK,KAAK,CAAC,KAAK;AAAA,IACzB,KAAK,QAAQ;AAEX,YAAM,oCAAoB,IAAA;AAC1B,iBAAW,OAAO,KAAK,MAAM;AAC3B,cAAM,QAAQ,6BAA6B,GAAG;AAC9C,YAAI,OAAO;AACT,wBAAc,IAAI,KAAK;AAAA,QACzB;AAAA,MACF;AAEA,aAAO,cAAc,SAAS,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC,IAAK;AAAA,IACpE;AAAA,IACA;AAEE,aAAO;AAAA,EAAA;AAEb;AAKA,SAAS,kBACP,MACA,WACA,aACA,eACA,WACA,aACA,+BACA,aACA,OACA,cACA,mBACA,qBACA,gBACA,oBAC6D;AAC7D,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK,iBAAiB;AACpB,YAAM,QAAQ,UAAU,KAAK,KAAK;AAClC,UAAI,CAAC,OAAO;AACV,cAAM,IAAIC,OAAAA;AAAAA,UACR,KAAK;AAAA,UACL,KAAK,WAAW;AAAA,UAChB,OAAO,KAAK,SAAS;AAAA,QAAA;AAAA,MAEzB;AACA,0BAAoB,KAAK,KAAK,IAAI,KAAK,WAAW;AAClD,aAAO,EAAE,OAAO,KAAK,OAAO,OAAO,cAAc,KAAK,WAAW,GAAA;AAAA,IACnE;AAAA,IACA,KAAK,YAAY;AAEf,YAAM,gBAAgB,aAAa,IAAI,KAAK,KAAK,KAAK,KAAK;AAG3D,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAMF,aAAO,OAAO,qBAAqB,eAAe,mBAAmB;AACrE,aAAO,OAAO,gBAAgB,eAAe,cAAc;AAW3D,YAAM,wBAAwB,aAAa,IAAI,KAAK,KAAK;AACzD,YAAM,iBAAiB,KAAK,MAAM,KAAK;AACvC,YAAM,qBACJ,CAAC,yBAAyB,KAAK,UAAU;AAE3C,UAAI,CAAC,oBAAoB;AACvB,mBAAW,CAAC,OAAO,WAAW,KAAK,eAAe,oBAAoB;AACpE,6BAAmB,IAAI,OAAO,WAAW;AAAA,QAC3C;AAAA,MACF;AAaA,YAAM,aAAa,OAAO,KAAK,eAAe,mBAAmB,EAAE;AAAA,QACjE,CAAC,UACC,eAAe,oBAAoB,KAAK,MACxC,eAAe;AAAA,MAAA;AAEnB,UAAI,cAAc,eAAe,KAAK,OAAO;AAC3C,uBAAe,KAAK,KAAK,IAAI;AAAA,MAC/B;AAGA,YAAM,gBAAgB,eAAe;AAIrC,YAAM,iBAAiB,cAAc;AAAA,QACnCf,MAAAA,IAAI,CAAC,SAAc;AACjB,gBAAM,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,IAAI;AACtC,iBAAO,CAAC,KAAK,KAAK;AAAA,QACpB,CAAC;AAAA,MAAA;AAGH,aAAO;AAAA,QACL,OAAO,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,cAAc,eAAe;AAAA,MAAA;AAAA,IAEjC;AAAA,IACA;AACE,YAAM,IAAIgB,OAAAA,+BAAgC,KAAa,IAAI;AAAA,EAAA;AAEjE;AAKA,SAAS,mBAAmB,UAAkB;AAC5C,SAAO,SACL,UAS0B;AAC1B,WAAO,SAAS;AAAA;AAAA,MAEdC,MAAAA,OAAO,CAAC,WAAW;AACjB,cAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI;AAC/B,cAAM,oBAAoB,OAAO,CAAC;AAClC,cAAM,sBAAsB,SAAS,CAAC;AAGtC,YAAI,aAAa,SAAS;AACxB,iBAAO,CAAC,EAAE,qBAAqB;AAAA,QACjC;AAEA,YAAI,aAAa,QAAQ;AACvB,iBAAO,CAAC,CAAC;AAAA,QACX;AAEA,YAAI,aAAa,SAAS;AACxB,iBAAO,CAAC,CAAC;AAAA,QACX;AAGA,eAAO;AAAA,MACT,CAAC;AAAA,MACDjB,MAAAA,IAAI,CAAC,WAAW;AACd,cAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI;AAC/B,cAAM,UAAU,OAAO,CAAC;AACxB,cAAM,oBAAoB,OAAO,CAAC;AAClC,cAAM,YAAY,SAAS,CAAC;AAC5B,cAAM,sBAAsB,SAAS,CAAC;AAGtC,cAAM,sBAAqC,CAAA;AAG3C,YAAI,mBAAmB;AACrB,iBAAO,OAAO,qBAAqB,iBAAiB;AAAA,QACtD;AAGA,YAAI,qBAAqB;AACvB,iBAAO,OAAO,qBAAqB,mBAAmB;AAAA,QACxD;AAGA,cAAM,YAAY,IAAI,OAAO,IAAI,SAAS;AAE1C,eAAO,CAAC,WAAW,mBAAmB;AAAA,MACxC,CAAC;AAAA,IAAA;AAAA,EAEL;AACF;AAYA,SAAS,wBACP,UACA,gBACA,iBAGqD;AAIrD,UAAQ,UAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,cAAc,QAAQ,YAAY,gBAAA;AAAA,IAC7C,KAAK;AACH,aAAO,EAAE,cAAc,UAAU,YAAY,eAAA;AAAA,IAC/C,KAAK;AAGH,aAAO,eAAe,OAAO,gBAAgB,OACzC,EAAE,cAAc,QAAQ,YAAY,gBAAA,IACpC,EAAE,cAAc,UAAU,YAAY,eAAA;AAAA,IAC5C;AACE,aAAO,EAAE,cAAc,QAAW,YAAY,OAAA;AAAA,EAAU;AAE9D;;"}
@@ -1,4 +1,4 @@
1
- export { BaseQueryBuilder, Query, type InitialQueryBuilder, type QueryBuilder, type Context, type Source, type GetResult, type InferResultType, type ExtractContext, type QueryResult, } from './builder/index.js';
1
+ export { BaseQueryBuilder, Query, type InitialQueryBuilder, type QueryBuilder, type Context, type ContextSchema, type Source, type GetResult, type InferResultType, type ExtractContext, type QueryResult, type SchemaFromSource, type InferCollectionType, type MergeContextWithJoinType, type MergeContextForJoinCallback, type ApplyJoinOptionalityToMergedSchema, type ResultTypeFromSelect, type WithResult, type JoinOnCallback, type RefsForContext, type WhereCallback, type OrderByCallback, type GroupByCallback, type SelectObject, type FunctionalHavingRow, type Prettify, } from './builder/index.js';
2
2
  export { eq, gt, gte, lt, lte, and, or, not, inArray, like, ilike, isUndefined, isNull, upper, lower, length, concat, coalesce, add, count, avg, sum, min, max, } from './builder/functions.js';
3
3
  export type { Ref } from './builder/types.js';
4
4
  export { compileQuery } from './compiler/index.js';
@@ -167,10 +167,15 @@ class CollectionConfigBuilder {
167
167
  return;
168
168
  }
169
169
  if (syncState.subscribedToAllCollections) {
170
+ let callbackCalled = false;
170
171
  while (syncState.graph.pendingWork()) {
171
172
  syncState.graph.run();
172
173
  syncState.flushPendingChanges?.();
173
174
  callback?.();
175
+ callbackCalled = true;
176
+ }
177
+ if (!callbackCalled) {
178
+ callback?.();
174
179
  }
175
180
  if (syncState.messagesCount === 0) {
176
181
  begin();