@@ -203,6 +203,97 @@ export function buildMessageFromSelectionSet(
203203 return message ;
204204}
205205
206+ /**
207+ * Gets or assigns a field number for a proto field
208+ */
209+ function getOrAssignFieldNumber (
210+ message : protobuf . Type ,
211+ protoFieldName : string ,
212+ fieldNumberManager ?: FieldNumberManager ,
213+ ) : number {
214+ const existingFieldNumber = fieldNumberManager ?. getFieldNumber ( message . name , protoFieldName ) ;
215+
216+ if ( existingFieldNumber !== undefined ) {
217+ return existingFieldNumber ;
218+ }
219+
220+ if ( fieldNumberManager ) {
221+ const fieldNumber = fieldNumberManager . getNextFieldNumber ( message . name ) ;
222+ fieldNumberManager . assignFieldNumber ( message . name , protoFieldName , fieldNumber ) ;
223+ return fieldNumber ;
224+ }
225+
226+ return message . fieldsArray . length + 1 ;
227+ }
228+
229+ /**
230+ * Resolves the final type name and repetition flag, handling nested list wrappers
231+ */
232+ function resolveTypeNameAndRepetition (
233+ baseTypeName : string ,
234+ protoTypeInfo : ProtoTypeInfo ,
235+ fieldType : GraphQLOutputType ,
236+ options ?: MessageBuilderOptions ,
237+ ) : { typeName : string ; isRepeated : boolean } {
238+ let typeName = baseTypeName ;
239+ let isRepeated = protoTypeInfo . isRepeated ;
240+
241+ if ( protoTypeInfo . requiresNestedWrapper && options ?. ensureNestedListWrapper ) {
242+ typeName = options . ensureNestedListWrapper ( fieldType ) as any ;
243+ isRepeated = false ; // Wrapper handles the repetition
244+ }
245+
246+ return { typeName, isRepeated } ;
247+ }
248+
249+ /**
250+ * Creates and configures a proto field with comments
251+ */
252+ function createProtoField (
253+ protoFieldName : string ,
254+ fieldNumber : number ,
255+ typeName : string ,
256+ isRepeated : boolean ,
257+ fieldDef : any ,
258+ options ?: MessageBuilderOptions ,
259+ ) : protobuf . Field {
260+ const protoField = new protobuf . Field ( protoFieldName , fieldNumber , typeName ) ;
261+
262+ if ( isRepeated ) {
263+ protoField . repeated = true ;
264+ }
265+
266+ if ( options ?. includeComments && fieldDef . description ) {
267+ protoField . comment = fieldDef . description ;
268+ }
269+
270+ return protoField ;
271+ }
272+
273+ /**
274+ * Ensures an enum type is created and added to the root
275+ */
276+ function ensureEnumCreated ( namedType : GraphQLEnumType , options : MessageBuilderOptions ) : void {
277+ if ( ! options . root ) {
278+ return ;
279+ }
280+
281+ const enumTypeName = namedType . name ;
282+
283+ // Initialize createdEnums in options if missing to ensure persistence across calls
284+ if ( ! options . createdEnums ) {
285+ options . createdEnums = new Set < string > ( ) ;
286+ }
287+
288+ if ( ! options . createdEnums . has ( enumTypeName ) ) {
289+ const protoEnum = buildEnumType ( namedType , {
290+ includeComments : options . includeComments ,
291+ } ) ;
292+ options . root . add ( protoEnum ) ;
293+ options . createdEnums . add ( enumTypeName ) ;
294+ }
295+ }
296+
206297/**
207298 * Processes a field selection and adds it to the message
208299 */
@@ -246,17 +337,15 @@ function processFieldSelection(
246337
247338 const fieldType = fieldDef . type ;
248339
249- // If the field has a selection set, we need a nested message
340+ // Determine the base type name based on whether we have a selection set
341+ let baseTypeName : string ;
342+
250343 if ( field . selectionSet ) {
344+ // Build nested message for object types
251345 const namedType = getNamedType ( fieldType ) ;
252346 if ( isObjectType ( namedType ) || isInterfaceType ( namedType ) || isUnionType ( namedType ) ) {
253- // Use simple name since message will be nested inside parent
254347 const nestedMessageName = upperFirst ( camelCase ( fieldName ) ) ;
255348
256- // For interfaces and unions, we use the type directly for processing
257- // Union types will only work with inline fragments that specify concrete types
258- const typeForSelection = namedType ;
259-
260349 const nestedOptions = {
261350 ...options ,
262351 _depth : ( options ?. _depth ?? 0 ) + 1 ,
@@ -265,122 +354,42 @@ function processFieldSelection(
265354 const nestedMessage = buildMessageFromSelectionSet (
266355 nestedMessageName ,
267356 field . selectionSet ,
268- typeForSelection ,
357+ namedType ,
269358 typeInfo ,
270359 nestedOptions ,
271360 ) ;
272361
273- // Add nested message to the parent message
274362 message . add ( nestedMessage ) ;
275-
276- // Get field number - check if already assigned from reconciliation
277- const existingFieldNumber = fieldNumberManager ?. getFieldNumber ( message . name , protoFieldName ) ;
278-
279- let fieldNumber : number ;
280- if ( existingFieldNumber !== undefined ) {
281- // Use existing field number from reconciliation
282- fieldNumber = existingFieldNumber ;
283- } else if ( fieldNumberManager ) {
284- // Get next field number and assign it
285- fieldNumber = fieldNumberManager . getNextFieldNumber ( message . name ) ;
286- fieldNumberManager . assignFieldNumber ( message . name , protoFieldName , fieldNumber ) ;
287- } else {
288- // No field number manager, use sequential numbering
289- fieldNumber = message . fieldsArray . length + 1 ;
290- }
291-
292- // Determine if field should be repeated
293- const protoTypeInfo = mapGraphQLTypeToProto ( fieldType , {
294- customScalarMappings : options ?. customScalarMappings ,
295- } ) ;
296-
297- // Handle nested list wrappers for nested messages
298- let finalTypeName = nestedMessageName ;
299- let isRepeated = protoTypeInfo . isRepeated ;
300-
301- if ( protoTypeInfo . requiresNestedWrapper && options ?. ensureNestedListWrapper ) {
302- // Create wrapper message and use its name
303- finalTypeName = options . ensureNestedListWrapper ( fieldType ) as any ;
304- isRepeated = false ; // Wrapper handles the repetition
305- }
306-
307- const protoField = new protobuf . Field ( protoFieldName , fieldNumber , finalTypeName ) ;
308-
309- if ( isRepeated ) {
310- protoField . repeated = true ;
311- }
312-
313- if ( options ?. includeComments && fieldDef . description ) {
314- protoField . comment = fieldDef . description ;
315- }
316-
317- message . add ( protoField ) ;
363+ baseTypeName = nestedMessageName ;
364+ } else {
365+ return ; // Shouldn't happen with valid GraphQL
318366 }
319367 } else {
320- // Scalar or enum field
368+ // Handle scalar/ enum fields
321369 const namedType = getNamedType ( fieldType ) ;
322370
323- // If this is an enum type, ensure it's added to the root
324371 if ( isEnumType ( namedType ) && options ?. root ) {
325- const enumTypeName = namedType . name ;
326-
327- // Initialize createdEnums in options if missing to ensure persistence across calls
328- if ( ! options . createdEnums ) {
329- options . createdEnums = new Set < string > ( ) ;
330- }
331- const createdEnums = options . createdEnums ;
332-
333- if ( ! createdEnums . has ( enumTypeName ) ) {
334- const protoEnum = buildEnumType ( namedType as GraphQLEnumType , {
335- includeComments : options . includeComments ,
336- } ) ;
337- options . root . add ( protoEnum ) ;
338- options . createdEnums . add ( enumTypeName ) ;
339- }
372+ ensureEnumCreated ( namedType as GraphQLEnumType , options ) ;
340373 }
341374
342375 const protoTypeInfo = mapGraphQLTypeToProto ( fieldType , {
343376 customScalarMappings : options ?. customScalarMappings ,
344377 } ) ;
378+ baseTypeName = protoTypeInfo . typeName ;
379+ }
345380
346- // Handle nested list wrappers
347- let finalTypeName = protoTypeInfo . typeName ;
348- let isRepeated = protoTypeInfo . isRepeated ;
349-
350- if ( protoTypeInfo . requiresNestedWrapper && options ?. ensureNestedListWrapper ) {
351- // Create wrapper message and use its name
352- finalTypeName = options . ensureNestedListWrapper ( fieldType ) as any ;
353- isRepeated = false ; // Wrapper handles the repetition
354- }
355-
356- // Get field number - check if already assigned from reconciliation
357- const existingFieldNumber = fieldNumberManager ?. getFieldNumber ( message . name , protoFieldName ) ;
358-
359- let fieldNumber : number ;
360- if ( existingFieldNumber !== undefined ) {
361- // Use existing field number from reconciliation
362- fieldNumber = existingFieldNumber ;
363- } else if ( fieldNumberManager ) {
364- // Get next field number and assign it
365- fieldNumber = fieldNumberManager . getNextFieldNumber ( message . name ) ;
366- fieldNumberManager . assignFieldNumber ( message . name , protoFieldName , fieldNumber ) ;
367- } else {
368- // No field number manager, use sequential numbering
369- fieldNumber = message . fieldsArray . length + 1 ;
370- }
381+ // Common logic for both branches
382+ const protoTypeInfo = mapGraphQLTypeToProto ( fieldType , {
383+ customScalarMappings : options ?. customScalarMappings ,
384+ } ) ;
371385
372- const protoField = new protobuf . Field ( protoFieldName , fieldNumber , finalTypeName ) ;
386+ const { typeName , isRepeated } = resolveTypeNameAndRepetition ( baseTypeName , protoTypeInfo , fieldType , options ) ;
373387
374- if ( isRepeated ) {
375- protoField . repeated = true ;
376- }
388+ const fieldNumber = getOrAssignFieldNumber ( message , protoFieldName , fieldNumberManager ) ;
377389
378- if ( options ?. includeComments && fieldDef . description ) {
379- protoField . comment = fieldDef . description ;
380- }
390+ const protoField = createProtoField ( protoFieldName , fieldNumber , typeName , isRepeated , fieldDef , options ) ;
381391
382- message . add ( protoField ) ;
383- }
392+ message . add ( protoField ) ;
384393}
385394
386395/**
0 commit comments