Skip to content

Conversation

@SoerenLinkFides
Copy link

@SoerenLinkFides SoerenLinkFides commented Oct 30, 2025

This PR adds fragment support to nestjs-query-graphql's simplifyResolveInfo method.
Previously, fields only included in a fragment were missing from the QueryService opts.resolveInfo field.

This does not affect inline fragments, as those are defined using a different Kind (Kind.INLINE_FRAGMENT).

There is no open issue for this PR.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed GraphQL fragment resolution in query processing to properly handle fragment spreads during resolution.

@coderabbitai
Copy link

coderabbitai bot commented Oct 30, 2025

Walkthrough

The parseFieldNodes function in graphql-resolve-info.utils.ts now inlines fragment spreads by resolving each FRAGMENT_SPREAD against resolveInfo.fragments and replacing it with the fragment's selection set. The reduction operates on the expanded selections instead of the original AST entries.

Changes

Cohort / File(s) Summary
Fragment Spread Inlining Logic
packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts
Modified parseFieldNodes to resolve and inline FRAGMENT_SPREAD entries against resolveInfo.fragments before processing, expanding the selection set prior to tree construction.

Sequence Diagram(s)

sequenceDiagram
    participant parseFieldNodes
    participant asts as Original AST
    participant fragments as resolveInfo.fragments
    participant inlined as Inlined Selections
    participant reduction as Reduction/Tree Build

    parseFieldNodes->>asts: Iterate field nodes
    loop For each selection
        alt Is FRAGMENT_SPREAD?
            parseFieldNodes->>fragments: Resolve fragment definition
            fragments-->>inlined: Return selectionSet or []
        else Is other selection
            asts-->>inlined: Keep as-is
        end
    end
    inlined->>reduction: Process inlined selections
    reduction-->>parseFieldNodes: Build resolve-info tree
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review the fragment spread resolution logic for edge cases (missing fragments, circular references)
  • Verify that inlining preserves all selection metadata and maintains correct tree structure
  • Check that the fallback to empty array for unresolved fragments doesn't introduce silent failures

Poem

🐰 Fragment spreads now unfold so wide,
Inlined with care, resolved with pride,
Before the tree takes its grand form,
Selections bloom, transformed, not torn,
A rabbit's refactor, ever so warm! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: support fragment parsing in parseFieldNodes" directly and accurately reflects the main change described in the objectives and the code summary. The title is concise, specific, and clearly identifies both the feature being added (fragment parsing) and the function being modified (parseFieldNodes). It accurately captures the primary objective of enabling fragment spread resolution in the parseFieldNodes function so that fields included only in fragments are properly captured. The title uses clear, descriptive language without vague terminology or unnecessary noise.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts (1)

73-77: Consider adding error handling for missing fragments.

When a fragment reference is not found in resolveInfo.fragments, the code silently returns an empty array. While this might be intentional, it could hide configuration errors or typos in fragment names. Consider logging a warning in development mode to help with debugging.

  if (ast.kind === Kind.FRAGMENT_SPREAD) {
    const fragment = resolveInfo.fragments[ast.name.value]
    if (fragment) {
      return fragment.selectionSet.selections
+   } else {
+     // Optional: log warning for missing fragment
+     console.warn(`Fragment "${ast.name.value}" not found in resolveInfo.fragments`)
    }
    return []
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d2689f and c7ed50a.

📒 Files selected for processing (1)
  • packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts (1 hunks)
🧰 Additional context used
🪛 ESLint
packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts

[error] 71-71: Replace ast with (ast)

(prettier/prettier)


[error] 73-73: Delete ;

(prettier/prettier)


[error] 75-75: Delete ;

(prettier/prettier)


[error] 77-77: Delete ;

(prettier/prettier)


[error] 79-79: Delete ;

(prettier/prettier)

🔇 Additional comments (1)
packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts (1)

82-82: Good: Reduce loop now operates on inlined fragments.

This change correctly processes the inlined fragment selections instead of the original ASTs. Once the fragment inlining logic is fixed to handle nested spreads and directives (see previous comments), this will properly include all fragment fields in the resolve info.

Comment on lines +71 to +80
const astsWithInlinedFragments = asts.flatMap(ast => {
if (ast.kind === Kind.FRAGMENT_SPREAD) {
const fragment = resolveInfo.fragments[ast.name.value];
if (fragment) {
return fragment.selectionSet.selections;
}
return [];
}
return ast;
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Nested fragment spreads are not handled recursively.

If a fragment contains another fragment spread, the nested spread will be silently ignored. For example:

fragment UserFields on User {
  id
  ...AddressFields  # This nested spread gets skipped
}

When UserFields is inlined, astsWithInlinedFragments becomes [id, ...AddressFields]. The reduce loop then processes ...AddressFields, but since it's still a FRAGMENT_SPREAD node, getFieldFromAST (line 96) returns undefined, causing it to be skipped at lines 97-99. This results in missing fields from nested fragments.

Fix: Make the inlining recursive to handle nested fragment spreads:

- const astsWithInlinedFragments = asts.flatMap(ast => {
-   if (ast.kind === Kind.FRAGMENT_SPREAD) {
-     const fragment = resolveInfo.fragments[ast.name.value];
-     if (fragment) {
-       return fragment.selectionSet.selections;
-     }
-     return [];
-   }
-   return ast;
- })
+ function inlineFragments(nodes: ReadonlyArray<SelectionNode>): SelectionNode[] {
+   return nodes.flatMap((ast) => {
+     if (ast.kind === Kind.FRAGMENT_SPREAD) {
+       const fragment = resolveInfo.fragments[ast.name.value]
+       if (fragment) {
+         return inlineFragments(fragment.selectionSet.selections)
+       }
+       return []
+     }
+     return ast
+   })
+ }
+
+ const astsWithInlinedFragments = inlineFragments(asts)
🧰 Tools
🪛 ESLint

[error] 71-71: Replace ast with (ast)

(prettier/prettier)


[error] 73-73: Delete ;

(prettier/prettier)


[error] 75-75: Delete ;

(prettier/prettier)


[error] 77-77: Delete ;

(prettier/prettier)


[error] 79-79: Delete ;

(prettier/prettier)

🤖 Prompt for AI Agents
In packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts around
lines 71 to 80, the current flatMap inlining only expands top-level fragment
spreads and ignores nested spreads; replace it with a recursive inliner that,
given an AST selection or array of selections, returns a flat array where each
FRAGMENT_SPREAD is replaced by the fragment's selectionSet selections processed
recursively; ensure you return [] for missing fragments and add a visited
fragment-name Set to prevent infinite recursion on cyclic fragments; use that
recursive function to produce astsWithInlinedFragments so all nested fragment
spreads are resolved before downstream processing.

⚠️ Potential issue | 🟠 Major

Major: Directives on fragment spreads are not respected.

Fragment spreads can have @include or @skip directives (e.g., ...UserFields @skip(if: $condition)). The current implementation inlines fragments without checking these directives, causing them to be ignored. This is incorrect because the GraphQL spec requires evaluating directives on fragment spreads before including their fields.

Fix: Check directives on the FRAGMENT_SPREAD node before inlining:

  const astsWithInlinedFragments = asts.flatMap((ast) => {
    if (ast.kind === Kind.FRAGMENT_SPREAD) {
+     // Check directives on the fragment spread itself
+     if (ast.directives && ast.directives.length) {
+       const { shouldInclude, shouldSkip } = getDirectiveResults(ast, resolveInfo)
+       if (shouldSkip || !shouldInclude) {
+         return []
+       }
+     }
+
      const fragment = resolveInfo.fragments[ast.name.value]
      if (fragment) {
        return fragment.selectionSet.selections
      }
      return []
    }
    return ast
  })

Note: This fix should be combined with the recursive inlining fix from the previous comment.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 71-71: Replace ast with (ast)

(prettier/prettier)


[error] 73-73: Delete ;

(prettier/prettier)


[error] 75-75: Delete ;

(prettier/prettier)


[error] 77-77: Delete ;

(prettier/prettier)


[error] 79-79: Delete ;

(prettier/prettier)

🤖 Prompt for AI Agents
In packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts around
lines 71-80, fragment spreads are inlined unconditionally which ignores
@include/@skip directives; update the FRAGMENT_SPREAD branch to evaluate the
spread's directives before inlining by calling GraphQL's directive inclusion
helper (e.g., shouldIncludeNode or equivalent) with the spread node and
resolveInfo.variableValues and only inline the fragment selectionSet if the node
should be included; ensure this change is applied together with the recursive
inlining fix from the previous comment so nested fragments and directives are
handled correctly.

⚠️ Potential issue | 🟡 Minor

Fix formatting issues.

The code has several Prettier/ESLint violations that should be fixed for consistency with the codebase style.

Apply these formatting fixes:

- const astsWithInlinedFragments = asts.flatMap(ast => {
+ const astsWithInlinedFragments = asts.flatMap((ast) => {
    if (ast.kind === Kind.FRAGMENT_SPREAD) {
-     const fragment = resolveInfo.fragments[ast.name.value];
+     const fragment = resolveInfo.fragments[ast.name.value]
      if (fragment) {
-       return fragment.selectionSet.selections;
+       return fragment.selectionSet.selections
      }
-     return [];
+     return []
    }
-   return ast;
+   return ast
  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const astsWithInlinedFragments = asts.flatMap(ast => {
if (ast.kind === Kind.FRAGMENT_SPREAD) {
const fragment = resolveInfo.fragments[ast.name.value];
if (fragment) {
return fragment.selectionSet.selections;
}
return [];
}
return ast;
})
const astsWithInlinedFragments = asts.flatMap((ast) => {
if (ast.kind === Kind.FRAGMENT_SPREAD) {
const fragment = resolveInfo.fragments[ast.name.value]
if (fragment) {
return fragment.selectionSet.selections
}
return []
}
return ast
})
🧰 Tools
🪛 ESLint

[error] 71-71: Replace ast with (ast)

(prettier/prettier)


[error] 73-73: Delete ;

(prettier/prettier)


[error] 75-75: Delete ;

(prettier/prettier)


[error] 77-77: Delete ;

(prettier/prettier)


[error] 79-79: Delete ;

(prettier/prettier)

🤖 Prompt for AI Agents
In packages/query-graphql/src/decorators/graphql-resolve-info.utils.ts around
lines 71 to 80, fix Prettier/ESLint formatting issues: add missing semicolons,
ensure consistent spacing and indentation, use parentheses for the arrow
function parameter (ast) if your linter requires them, put braces/newlines so
each return sits on its own line, and ensure consistent trailing commas where
the codebase expects them; reformat the block so it conforms to the project's
Prettier/ESLint rules without changing logic.

@nx-cloud
Copy link

nx-cloud bot commented Oct 30, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit c7ed50a

Command Status Duration Result
nx run-many --target=lint --all ❌ Failed 16s View ↗
nx run-many --target=e2e --all ✅ Succeeded 1m 53s View ↗
nx run-many --target=test --all ✅ Succeeded 42s View ↗
nx run-many --target=build --all ✅ Succeeded 15s View ↗
nx run workspace:version ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-30 20:37:27 UTC

@SoerenLinkFides
Copy link
Author

Sorry, seems this topic is a little more complicated.
Will try and resolve the issues in this PR in the next few days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant