1+ import { Injectable , Logger } from '@nestjs/common' ;
2+ import { gql } from '@apollo/client/core/index.js' ;
3+ import { parse , print , visit } from 'graphql' ;
4+
5+ import { InternalClientService } from '../internal-rpc/internal.client.js' ;
6+
7+ interface GraphQLExecutor {
8+ execute ( params : {
9+ query : string
10+ variables ?: Record < string , any >
11+ operationName ?: string
12+ operationType ?: 'query' | 'mutation' | 'subscription'
13+ } ) : Promise < any >
14+ stopSubscription ?( operationId : string ) : Promise < void >
15+ }
16+
17+ /**
18+ * Local GraphQL executor that maps remote queries to local API calls
19+ */
20+ @Injectable ( )
21+ export class LocalGraphQLExecutor implements GraphQLExecutor {
22+ private logger = new Logger ( 'LocalGraphQLExecutor' ) ;
23+
24+ constructor ( private readonly internalClient : InternalClientService ) { }
25+
26+ async execute ( params : {
27+ query : string
28+ variables ?: Record < string , any >
29+ operationName ?: string
30+ operationType ?: 'query' | 'mutation' | 'subscription'
31+ } ) : Promise < any > {
32+ const { query, variables, operationName, operationType } = params ;
33+
34+ try {
35+ this . logger . debug ( `Executing ${ operationType } operation: ${ operationName || 'unnamed' } ` ) ;
36+ this . logger . verbose ( `Query: ${ query } ` ) ;
37+ this . logger . verbose ( `Variables: ${ JSON . stringify ( variables ) } ` ) ;
38+
39+ // Transform remote query to local query by removing "remote" prefixes
40+ const localQuery = this . transformRemoteQueryToLocal ( query ) ;
41+
42+ // Execute the transformed query against local API
43+ const client = await this . internalClient . getClient ( ) ;
44+ const result = await client . query ( {
45+ query : gql `${ localQuery } ` ,
46+ variables,
47+ } ) ;
48+
49+ return {
50+ data : result . data ,
51+ } ;
52+ } catch ( error : any ) {
53+ this . logger . error ( `GraphQL execution error: ${ error ?. message } ` ) ;
54+ return {
55+ errors : [
56+ {
57+ message : error ?. message || 'Unknown error' ,
58+ extensions : { code : 'EXECUTION_ERROR' } ,
59+ } ,
60+ ] ,
61+ } ;
62+ }
63+ }
64+
65+ /**
66+ * Transform remote GraphQL query to local query by removing "remote" prefixes
67+ */
68+ private transformRemoteQueryToLocal ( query : string ) : string {
69+ try {
70+ // Parse the GraphQL query
71+ const document = parse ( query ) ;
72+
73+ // Transform the document by removing "remote" prefixes
74+ const transformedDocument = visit ( document , {
75+ // Transform operation names (e.g., remoteGetDockerInfo -> getDockerInfo)
76+ OperationDefinition : ( node ) => {
77+ if ( node . name ?. value . startsWith ( 'remote' ) ) {
78+ return {
79+ ...node ,
80+ name : {
81+ ...node . name ,
82+ value : this . removeRemotePrefix ( node . name . value ) ,
83+ } ,
84+ } ;
85+ }
86+ return node ;
87+ } ,
88+ // Transform field names (e.g., remoteGetDockerInfo -> docker, remoteGetVms -> vms)
89+ Field : ( node ) => {
90+ if ( node . name . value . startsWith ( 'remote' ) ) {
91+ return {
92+ ...node ,
93+ name : {
94+ ...node . name ,
95+ value : this . transformRemoteFieldName ( node . name . value ) ,
96+ } ,
97+ } ;
98+ }
99+ return node ;
100+ } ,
101+ } ) ;
102+
103+ // Convert back to string
104+ return print ( transformedDocument ) ;
105+ } catch ( error ) {
106+ this . logger . error ( `Failed to parse/transform GraphQL query: ${ error } ` ) ;
107+ throw error ;
108+ }
109+ }
110+
111+ /**
112+ * Remove "remote" prefix from operation names
113+ */
114+ private removeRemotePrefix ( name : string ) : string {
115+ if ( name . startsWith ( 'remote' ) ) {
116+ // remoteGetDockerInfo -> getDockerInfo
117+ return name . slice ( 6 ) ; // Remove "remote"
118+ }
119+ return name ;
120+ }
121+
122+ /**
123+ * Transform remote field names to local equivalents
124+ */
125+ private transformRemoteFieldName ( fieldName : string ) : string {
126+ // Handle common patterns
127+ if ( fieldName === 'remoteGetDockerInfo' ) {
128+ return 'docker' ;
129+ }
130+ if ( fieldName === 'remoteGetVms' ) {
131+ return 'vms' ;
132+ }
133+ if ( fieldName === 'remoteGetSystemInfo' ) {
134+ return 'system' ;
135+ }
136+
137+ // Generic transformation: remove "remoteGet" and convert to camelCase
138+ if ( fieldName . startsWith ( 'remoteGet' ) ) {
139+ const baseName = fieldName . slice ( 9 ) ; // Remove "remoteGet"
140+ return baseName . charAt ( 0 ) . toLowerCase ( ) + baseName . slice ( 1 ) ;
141+ }
142+
143+ // Remove "remote" prefix as fallback
144+ if ( fieldName . startsWith ( 'remote' ) ) {
145+ const baseName = fieldName . slice ( 6 ) ; // Remove "remote"
146+ return baseName . charAt ( 0 ) . toLowerCase ( ) + baseName . slice ( 1 ) ;
147+ }
148+
149+ return fieldName ;
150+ }
151+
152+ async stopSubscription ( operationId : string ) : Promise < void > {
153+ this . logger . debug ( `Stopping subscription: ${ operationId } ` ) ;
154+ // Subscription cleanup logic would go here
155+ }
156+ }
0 commit comments