11import { CollectionImpl } from "../../collection.js"
2- import { CollectionRef , QueryRef } from "../ir.js"
2+ import {
3+ Aggregate as AggregateExpr ,
4+ CollectionRef ,
5+ Func as FuncExpr ,
6+ PropRef ,
7+ QueryRef ,
8+ Value as ValueExpr ,
9+ isExpressionLike ,
10+ } from "../ir.js"
311import {
412 InvalidSourceError ,
513 JoinConditionMustBeEqualityError ,
614 OnlyOneSourceAllowedError ,
715 QueryMustHaveFromClauseError ,
816 SubQueryMustHaveFromClauseError ,
917} from "../../errors.js"
10- import { createRefProxy , isRefProxy , toExpression } from "./ref-proxy.js"
18+ import { createRefProxy , toExpression } from "./ref-proxy.js"
1119import type { NamespacedRow } from "../../types.js"
1220import type {
1321 Aggregate ,
@@ -26,7 +34,7 @@ import type {
2634 MergeContextWithJoinType ,
2735 OrderByCallback ,
2836 OrderByOptions ,
29- RefProxyForContext ,
37+ RefsForContext ,
3038 ResultTypeFromSelect ,
3139 SchemaFromSource ,
3240 SelectObject ,
@@ -152,7 +160,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
152160 // Create a temporary context for the callback
153161 const currentAliases = this . _getCurrentAliases ( )
154162 const newAliases = [ ...currentAliases , alias ]
155- const refProxy = createRefProxy ( newAliases ) as RefProxyForContext <
163+ const refProxy = createRefProxy ( newAliases ) as RefsForContext <
156164 MergeContextForJoinCallback < TContext , SchemaFromSource < TSource > >
157165 >
158166
@@ -324,7 +332,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
324332 */
325333 where ( callback : WhereCallback < TContext > ) : QueryBuilder < TContext > {
326334 const aliases = this . _getCurrentAliases ( )
327- const refProxy = createRefProxy ( aliases ) as RefProxyForContext < TContext >
335+ const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
328336 const expression = callback ( refProxy )
329337
330338 const existingWhere = this . query . where || [ ]
@@ -365,7 +373,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
365373 */
366374 having ( callback : WhereCallback < TContext > ) : QueryBuilder < TContext > {
367375 const aliases = this . _getCurrentAliases ( )
368- const refProxy = createRefProxy ( aliases ) as RefProxyForContext < TContext >
376+ const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
369377 const expression = callback ( refProxy )
370378
371379 const existingHaving = this . query . having || [ ]
@@ -411,43 +419,16 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
411419 * ```
412420 */
413421 select < TSelectObject extends SelectObject > (
414- callback : ( refs : RefProxyForContext < TContext > ) => TSelectObject
422+ callback : ( refs : RefsForContext < TContext > ) => TSelectObject
415423 ) : QueryBuilder < WithResult < TContext , ResultTypeFromSelect < TSelectObject > > > {
416424 const aliases = this . _getCurrentAliases ( )
417- const refProxy = createRefProxy ( aliases ) as RefProxyForContext < TContext >
425+ const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
418426 const selectObject = callback ( refProxy )
419-
420- // Check if any tables were spread during the callback
421- const spreadSentinels = ( refProxy as any ) . __spreadSentinels as Set < string >
422-
423- // Convert the select object to use expressions, including spread sentinels
424- const select : Record < string , BasicExpression | Aggregate > = { }
425-
426- // First, add spread sentinels for any tables that were spread
427- for ( const spreadAlias of spreadSentinels ) {
428- const sentinelKey = `__SPREAD_SENTINEL__${ spreadAlias } `
429- select [ sentinelKey ] = toExpression ( spreadAlias ) // Use alias as a simple reference
430- }
431-
432- // Then add the explicit select fields
433- for ( const [ key , value ] of Object . entries ( selectObject ) ) {
434- if ( isRefProxy ( value ) ) {
435- select [ key ] = toExpression ( value )
436- } else if (
437- typeof value === `object` &&
438- value !== null &&
439- `type` in value &&
440- ( value . type === `agg` || value . type === `func` )
441- ) {
442- select [ key ] = value as BasicExpression | Aggregate
443- } else {
444- select [ key ] = toExpression ( value )
445- }
446- }
427+ const select = buildNestedSelect ( selectObject )
447428
448429 return new BaseQueryBuilder ( {
449430 ...this . query ,
450- select,
431+ select : select ,
451432 fnSelect : undefined , // remove the fnSelect clause if it exists
452433 } ) as any
453434 }
@@ -483,7 +464,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
483464 options : OrderByDirection | OrderByOptions = `asc`
484465 ) : QueryBuilder < TContext > {
485466 const aliases = this . _getCurrentAliases ( )
486- const refProxy = createRefProxy ( aliases ) as RefProxyForContext < TContext >
467+ const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
487468 const result = callback ( refProxy )
488469
489470 const opts : CompareOptions =
@@ -551,7 +532,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
551532 */
552533 groupBy ( callback : GroupByCallback < TContext > ) : QueryBuilder < TContext > {
553534 const aliases = this . _getCurrentAliases ( )
554- const refProxy = createRefProxy ( aliases ) as RefProxyForContext < TContext >
535+ const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
555536 const result = callback ( refProxy )
556537
557538 const newExpressions = Array . isArray ( result )
@@ -760,6 +741,43 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
760741 }
761742}
762743
744+ // Helper to ensure we have a BasicExpression/Aggregate for a value
745+ function toExpr ( value : any ) : BasicExpression | Aggregate {
746+ if ( value === undefined ) return toExpression ( null )
747+ if (
748+ value instanceof AggregateExpr ||
749+ value instanceof FuncExpr ||
750+ value instanceof PropRef ||
751+ value instanceof ValueExpr
752+ ) {
753+ return value as BasicExpression | Aggregate
754+ }
755+ return toExpression ( value )
756+ }
757+
758+ function isPlainObject ( value : any ) : value is Record < string , any > {
759+ return (
760+ value !== null &&
761+ typeof value === `object` &&
762+ ! isExpressionLike ( value ) &&
763+ ! value . __refProxy
764+ )
765+ }
766+
767+ function buildNestedSelect ( obj : any ) : any {
768+ if ( ! isPlainObject ( obj ) ) return toExpr ( obj )
769+ const out : Record < string , any > = { }
770+ for ( const [ k , v ] of Object . entries ( obj ) ) {
771+ if ( typeof k === `string` && k . startsWith ( `__SPREAD_SENTINEL__` ) ) {
772+ // Preserve sentinel key and its value (value is unimportant at compile time)
773+ out [ k ] = v
774+ continue
775+ }
776+ out [ k ] = buildNestedSelect ( v )
777+ }
778+ return out
779+ }
780+
763781// Internal function to build a query from a callback
764782// used by liveQueryCollectionOptions.query
765783export function buildQuery < TContext extends Context > (
@@ -799,4 +817,4 @@ export type ExtractContext<T> =
799817 : never
800818
801819// Export the types from types.ts for convenience
802- export type { Context , Source , GetResult , Ref } from "./types.js"
820+ export type { Context , Source , GetResult , RefLeaf as Ref } from "./types.js"
0 commit comments