33 * Uses AST-based approach for all query generation
44 */
55import * as t from 'gql-ast' ;
6- import { OperationTypeNode , print } from 'graphql' ;
6+ import { Kind , OperationTypeNode , print } from 'graphql' ;
77import type { ArgumentNode , FieldNode , VariableDefinitionNode } from 'graphql' ;
88
99import { TypedDocumentString } from '../client/typed-document' ;
@@ -296,6 +296,7 @@ export function buildSelect(
296296 tableList ,
297297 selection ,
298298 options ,
299+ options . relationFieldMap ,
299300 ) ;
300301
301302 return new TypedDocumentString ( queryString , { } ) as TypedDocumentString <
@@ -352,6 +353,7 @@ function generateSelectQueryAST(
352353 allTables : CleanTable [ ] ,
353354 selection : QuerySelectionOptions | null ,
354355 options : QueryOptions ,
356+ relationFieldMap ?: Record < string , string | null > ,
355357) : string {
356358 const pluralName = toCamelCasePlural ( table . name , table ) ;
357359
@@ -360,6 +362,7 @@ function generateSelectQueryAST(
360362 table ,
361363 allTables ,
362364 selection ,
365+ relationFieldMap ,
363366 ) ;
364367
365368 // Build the query AST
@@ -529,6 +532,7 @@ function generateFieldSelectionsFromOptions(
529532 table : CleanTable ,
530533 allTables : CleanTable [ ] ,
531534 selection : QuerySelectionOptions | null ,
535+ relationFieldMap ?: Record < string , string | null > ,
532536) : FieldNode [ ] {
533537 const DEFAULT_NESTED_RELATION_FIRST = 20 ;
534538
@@ -550,6 +554,11 @@ function generateFieldSelectionsFromOptions(
550554 const fieldSelections : FieldNode [ ] = [ ] ;
551555
552556 Object . entries ( selection ) . forEach ( ( [ fieldName , fieldOptions ] ) => {
557+ const resolvedField = resolveSelectionFieldName ( fieldName , relationFieldMap ) ;
558+ if ( ! resolvedField ) {
559+ return ; // Field mapped to null — omit it
560+ }
561+
553562 if ( fieldOptions === true ) {
554563 // Check if this field requires subfield selection
555564 const field = table . fields . find ( ( f ) => f . name === fieldName ) ;
@@ -558,7 +567,9 @@ function generateFieldSelectionsFromOptions(
558567 fieldSelections . push ( getCustomAstForCleanField ( field ) ) ;
559568 } else {
560569 // Simple field selection for scalar fields
561- fieldSelections . push ( t . field ( { name : fieldName } ) ) ;
570+ fieldSelections . push (
571+ createFieldSelectionNode ( resolvedField . name , resolvedField . alias ) ,
572+ ) ;
562573 }
563574 } else if ( typeof fieldOptions === 'object' && fieldOptions . select ) {
564575 // Nested field selection (for relation fields)
@@ -591,17 +602,18 @@ function generateFieldSelectionsFromOptions(
591602 ) {
592603 // For hasMany/manyToMany relations, wrap selections in nodes { ... }
593604 fieldSelections . push (
594- t . field ( {
595- name : fieldName ,
596- args : [
605+ createFieldSelectionNode (
606+ resolvedField . name ,
607+ resolvedField . alias ,
608+ [
597609 t . argument ( {
598610 name : 'first' ,
599611 value : t . intValue ( {
600612 value : DEFAULT_NESTED_RELATION_FIRST . toString ( ) ,
601613 } ) ,
602614 } ) ,
603615 ] ,
604- selectionSet : t . selectionSet ( {
616+ t . selectionSet ( {
605617 selections : [
606618 t . field ( {
607619 name : 'nodes' ,
@@ -611,17 +623,19 @@ function generateFieldSelectionsFromOptions(
611623 } ) ,
612624 ] ,
613625 } ) ,
614- } ) ,
626+ ) ,
615627 ) ;
616628 } else {
617629 // For belongsTo/hasOne relations, use direct selection
618630 fieldSelections . push (
619- t . field ( {
620- name : fieldName ,
621- selectionSet : t . selectionSet ( {
631+ createFieldSelectionNode (
632+ resolvedField . name ,
633+ resolvedField . alias ,
634+ undefined ,
635+ t . selectionSet ( {
622636 selections : nestedSelections ,
623637 } ) ,
624- } ) ,
638+ ) ,
625639 ) ;
626640 }
627641 }
@@ -630,6 +644,60 @@ function generateFieldSelectionsFromOptions(
630644 return fieldSelections ;
631645}
632646
647+ // ---------------------------------------------------------------------------
648+ // Field aliasing helpers (back-ported from Dashboard query-generator.ts)
649+ // ---------------------------------------------------------------------------
650+
651+ /**
652+ * Resolve a field name through the optional relationFieldMap.
653+ * Returns `null` if the field should be omitted (mapped to null).
654+ * Returns `{ name, alias? }` where alias is set when the mapped name differs.
655+ */
656+ function resolveSelectionFieldName (
657+ fieldName : string ,
658+ relationFieldMap ?: Record < string , string | null > ,
659+ ) : { name : string ; alias ?: string } | null {
660+ if ( ! relationFieldMap || ! ( fieldName in relationFieldMap ) ) {
661+ return { name : fieldName } ;
662+ }
663+
664+ const mappedFieldName = relationFieldMap [ fieldName ] ;
665+ if ( ! mappedFieldName ) {
666+ return null ; // mapped to null → omit
667+ }
668+
669+ if ( mappedFieldName === fieldName ) {
670+ return { name : fieldName } ;
671+ }
672+
673+ return { name : mappedFieldName , alias : fieldName } ;
674+ }
675+
676+ /**
677+ * Create a field AST node with optional alias support.
678+ * When alias is provided and differs from name, a GraphQL alias is emitted:
679+ * `alias: name { … }` instead of `name { … }`
680+ */
681+ function createFieldSelectionNode (
682+ name : string ,
683+ alias ?: string ,
684+ args ?: ArgumentNode [ ] ,
685+ selectionSet ?: ReturnType < typeof t . selectionSet > ,
686+ ) : FieldNode {
687+ const node = t . field ( { name, args, selectionSet } ) ;
688+ if ( ! alias || alias === name ) {
689+ return node ;
690+ }
691+
692+ return {
693+ ...node ,
694+ alias : {
695+ kind : Kind . NAME ,
696+ value : alias ,
697+ } ,
698+ } ;
699+ }
700+
633701/**
634702 * Get relation information for a field
635703 */
0 commit comments