Skip to content

Conversation

enisdenjo
Copy link
Collaborator

Improves #8733 by automatically finding the appropriate subgraph to resolve missing fields.

import { defineConfig, loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
  subgraphs: [
    {
      sourceHandler: loadGraphQLHTTPSubgraph('products', {
        endpoint: `http://localhost:3000/graphql`
      })
    }
  ],
  additionalTypeDefs: /* GraphQL */ `
    extend schema {
      subscription: Subscription
    }
    type Subscription {
      newProduct: Product! @resolveTo(
        pubsubTopic: "new_product"
-       sourceName: "products"
      )
    }
  `
})

Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Auto-detects subgraph for subscription events; no need to specify sourceName in @resolveTo.
    • Simplified additional type definitions for subscriptions.
  • Bug Fixes

    • More resilient resolver selection with graceful fallbacks instead of errors.
    • Handles undefined context and safer pubsub access.
  • Documentation

    • Updated examples to demonstrate auto-detection in subscriptions.
  • Tests

    • Added tests for selection set handling and subscription type merging.
  • Chores

    • Prepared a patch release.

Walkthrough

Implements automatic subgraph detection for subscription-resolved fields by removing the need for sourceName in @resolveTo, adds selection-set utilities, updates resolver logic to pick a subschema via selection-set containment with graceful fallbacks, updates an e2e example, and adds tests and changesets.

Changes

Cohort / File(s) Summary
Changesets
.changeset/plain-roses-sort.md, .changeset/puny-geckos-attack.md
Add patch release entries for @graphql-mesh/utils; document auto-merge types from subscriptions and safer handling when context can be undefined.
E2E example: subscriptions type merging
e2e/subscriptions-type-merging/mesh.config.ts, e2e/subscriptions-type-merging/services/products.ts
Remove sourceName from @resolveTo on Subscription.newProduct; add a second federation @key on Product and make __resolveReference resilient to missing id.
Resolver core logic
packages/legacy/utils/src/resolve-additional-resolvers.ts
Replace strict sourceName/stitching path with selection-set–based subschema detection using selectionSetOfData/containsSelectionSet; fallback to payload when no match; rename helper, import print, use context?.pubsub, and adjust resolver invocation to support selection-set matching.
Selection-set utilities
packages/legacy/utils/src/selectionSet.ts
Add containsSelectionSet(requiredSelSet, selSet) and selectionSetOfData(data) to build and compare GraphQL selection sets from plain objects.
Tests
packages/legacy/utils/test/resolve-additional-resolvers.spec.ts, packages/legacy/utils/test/selectionSet.spec.ts
Add tests validating subscription resolution with auto-detected subschema and selection-set containment behavior; include a skipped test for partial-field-resolution behavior.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Gateway as Mesh Gateway
  participant AddlRes as AdditionalResolver
  participant Utils as SelectionSetUtils
  participant Stitch as StitchingInfo
  participant Subschema as ChosenSubschema

  Client->>Gateway: Subscribe to topic (newProduct)
  Gateway->>AddlRes: Receive payload from pubsub
  rect rgba(220,235,255,0.35)
    AddlRes->>Utils: selectionSetOfData(payload)
    Utils-->>AddlRes: availableSelSet
    AddlRes->>Stitch: Read mergedTypes.selectionSets
    loop find match
      AddlRes->>Utils: containsSelectionSet(requiredSelSet, availableSelSet)
      Utils-->>AddlRes: match? (true/false)
    end
  end
  alt Match found
    AddlRes->>Subschema: Invoke subschema resolver for merged type
    Subschema-->>AddlRes: Enriched result
  else No match or no stitching info
    note right of AddlRes: Fallback — return original payload unchanged
  end
  AddlRes-->>Gateway: Final event payload
  Gateway-->>Client: Emit subscription event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ardatan

Poem

I twitch my ears at streams that flow,
No names to chase—yet still I know.
Selection sets whisper where to bind,
I hop the graphs and stitch the find.
Carrots shared as payloads grow—hooray! 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch automatch

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@enisdenjo enisdenjo requested a review from ardatan August 29, 2025 15:23
Copy link
Contributor

github-actions bot commented Aug 29, 2025

Apollo Federation Subgraph Compatibility Results

Federation 1 Support Federation 2 Support
_service🟢
@key (single)🟢
@key (multi)🟢
@key (composite)🟢
repeatable @key🟢
@requires🟢
@provides🟢
federated tracing🟢
@link🟢
@shareable🟢
@tag🟢
@override🟢
@inaccessible🟢
@composeDirective🟢
@interfaceObject🟢

Learn more:

Copy link
Contributor

github-actions bot commented Aug 29, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-mesh/cache-cfw-kv 0.105.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-file 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-inmemory-lru 0.8.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-localforage 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-redis 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-upstash-redis 0.1.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/compose-cli 1.4.15-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/fusion-composition 0.8.14-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/include 0.3.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/apollo-link 0.106.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/cli 0.100.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/config 0.108.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/graphql 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/grpc 0.108.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/json-schema 0.109.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/mongoose 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/mysql 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/neo4j 0.107.8-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/odata 0.106.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/openapi 0.109.17-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/postgraphile 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/raml 0.109.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/soap 0.107.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/supergraph 0.10.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/thrift 0.106.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/tuql 0.105.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/http 0.106.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-bare 0.105.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-stitching 0.105.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/migrate-config-cli 1.6.16-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/runtime 0.106.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/store 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-cache 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-encapsulate 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-extend 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-federation 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-filter-schema 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hive 0.104.12-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hoist-field 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-naming-convention 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prefix 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prune 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rate-limit 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rename 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-replace-field 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-resolvers-composition 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-transfer-schema 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-type-merging 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/types 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/urql-exchange 0.106.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/utils 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/json-schema 0.109.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/mysql 0.9.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/neo4j 0.11.8-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/odata 0.2.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/openapi 0.109.17-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/raml 0.109.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/soap 0.107.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/sqlite 0.8.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@omnigraph/thrift 0.9.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-deduplicate-request 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-hive 0.104.12-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-cache 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-details-extensions 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-jit 0.2.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-live-query 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-mock 0.105.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-newrelic 0.104.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-field-permissions 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-headers 1.4.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-rate-limit 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-response-cache 0.104.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-snapshot 0.104.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-statsd 0.104.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-grpc 0.3.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-mysql 0.9.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-neo4j 0.10.8-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-odata 0.2.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-rest 0.9.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-soap 0.10.11-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-sqlite 0.9.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-thrift 0.9.10-alpha-20250829192725-2edabf4a4c315791c6e40eef7c0b28c4911b985e npm ↗︎ unpkg ↗︎

Copy link
Contributor

github-actions bot commented Aug 29, 2025

💻 Website Preview

The latest changes are available as preview in:

Copy link
Contributor

@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: 5

🧹 Nitpick comments (7)
.changeset/plain-roses-sort.md (1)

7-7: Fix terminology: it's an argument on @resolveTo, not a directive.

Rephrase “the sourceName directive does not need to be provided to the @resolveTo directive” to “the sourceName argument no longer needs to be provided on @resolveTo.”

Apply:

-This means that the `sourceName` directive does not need to be provided to the `@resolveTo` directive, instead the resolver will automatically find the subgraph that requires fields available in the subscription event.
+This means the `sourceName` argument no longer needs to be provided on `@resolveTo`. The resolver will automatically pick the subgraph that can satisfy the required fields present in the subscription event.
packages/legacy/utils/src/containsSelectionSet.ts (2)

24-26: Correct misleading comment.

The selection with a nested selectionSet is an object, not “scalar”.

-              // required field is a scalar, but the required selection under the same name is scalar
+              // required field is a scalar, but the selection under the same name is an object

6-9: Normalize/merge selection sets to match GraphQL field-merge semantics.

Current O(n²) scan can falsely return false if the same field is selected multiple times with disjoint sub-selections (GraphQL merges them). Consider pre-normalizing selSet (and requiredSelSet) by merging repeated fields and inline fragments before containment checks.

I can provide a normalization helper leveraging @graphql-tools/utils merge utilities if desired.

Also applies to: 10-19, 29-36, 58-60

packages/legacy/utils/src/resolve-additional-resolvers.ts (4)

23-23: Make Subschema a type-only import to avoid unnecessary runtime dependency.

No runtime use of Subschema is present; importing it as a value pulls extra code into bundles.

-import { Subschema, type MergedTypeResolver, type StitchingInfo } from '@graphql-tools/delegate';
+import type { Subschema, MergedTypeResolver, StitchingInfo } from '@graphql-tools/delegate';

237-241: Fix reversed comment to match containment logic.

The implementation checks if the client’s selection contains all required fields, not the other way around.

-              if (containsSelectionSet(requiredSelSet, availableSelSet)) {
-                // all of the fields of the requesting selection set is exist in the required selection set
+              if (containsSelectionSet(requiredSelSet, availableSelSet)) {
+                // availableSelSet contains all fields required by requiredSelSet

259-260: Pass named type to resolver to avoid NonNull/List wrappers.

Merged resolvers typically expect a named type. This avoids surprises across toolchain versions.

-                  info.returnType,
+                  getNamedType(info.returnType),

Please run a local type check to confirm the expected signature of MergedTypeResolver in your installed @graphql-tools/delegate.


231-243: Resolver tie-breaker heuristic (optional).

If multiple subschemas match, consider preferring the one with the smallest required selection set (or greatest coverage score) to minimize over-fetch and ambiguity.

Happy to propose a small comparator that ranks candidates by requiredSelSet.selections.length (recursively counted).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff7aa1d and 6e5869f.

📒 Files selected for processing (6)
  • .changeset/plain-roses-sort.md (1 hunks)
  • e2e/subscriptions-type-merging/mesh.config.ts (1 hunks)
  • e2e/subscriptions-type-merging/services/products.ts (2 hunks)
  • packages/legacy/utils/src/containsSelectionSet.ts (1 hunks)
  • packages/legacy/utils/src/resolve-additional-resolvers.ts (2 hunks)
  • packages/legacy/utils/test/containsSelectionSet.spec.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T17:11:35.160Z
Learnt from: enisdenjo
PR: ardatan/graphql-mesh#8716
File: packages/legacy/types/src/pubsub.ts:64-79
Timestamp: 2025-08-22T17:11:35.160Z
Learning: In the GraphQL Mesh codebase, when implementing MeshPubSub interfaces, the optional `options` parameter in the subscribe method may be intentionally omitted if it's unused and irrelevant for the specific implementation, even if it causes a minor TypeScript interface mismatch.

Applied to files:

  • e2e/subscriptions-type-merging/mesh.config.ts
  • .changeset/plain-roses-sort.md
  • packages/legacy/utils/src/resolve-additional-resolvers.ts
🧬 Code graph analysis (2)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)
packages/legacy/utils/src/containsSelectionSet.ts (1)
  • containsSelectionSet (6-60)
packages/legacy/utils/test/containsSelectionSet.spec.ts (1)
packages/legacy/utils/src/containsSelectionSet.ts (1)
  • containsSelectionSet (6-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: release / snapshot
  • GitHub Check: deployment
  • GitHub Check: apollo-federation-compatibility
  • GitHub Check: integration / node 20
  • GitHub Check: e2e / node v22
  • GitHub Check: e2e / node v20
  • GitHub Check: unit / node 22
  • GitHub Check: e2e / node v18
  • GitHub Check: integration / node 18
  • GitHub Check: integration / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: check
🔇 Additional comments (4)
.changeset/plain-roses-sort.md (1)

32-33: Document behavior in edge cases and backward compatibility.

Add a short note covering:

  • When multiple subgraphs match, how tie-breaking works.
  • Whether specifying sourceName is still accepted and how conflicts are resolved.
  • Known limitations (fragment spreads not supported unless selection sets are pre-expanded).

Proposed addition:

+Notes
+- If multiple subgraphs match, the resolver picks the first matching subgraph (implementation-defined). Consider making the selection set more specific to avoid ambiguity.
+- Existing configs that specify `sourceName` remain supported; when both are present, auto-detection is bypassed (or `sourceName` takes precedence) — verify and document the actual behavior.
+- Limitations: selection-set containment currently supports fields and inline fragments; fragment spreads must be pre-expanded by the caller.
e2e/subscriptions-type-merging/mesh.config.ts (2)

19-19: LGTM: removal of sourceName aligns with auto-detection.


14-21: Verify payload includes a valid key for Product.

With auto-detection, ensure the emitted new_product payload contains at least one federation key (id or name) requested by clients; otherwise __resolveReference may fabricate placeholder data.

If needed, update the publisher to include id or name, and add an e2e assertion that a client selecting only id can resolve the entity end-to-end.

packages/legacy/utils/src/resolve-additional-resolvers.ts (1)

215-226: Good fallback strategy when stitching info or merged type data is absent.

Gracefully returning the payload preserves existing behavior for non-merged or non-stitching schemas.

Copy link
Contributor

@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: 1

♻️ Duplicate comments (1)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)

227-241: Handle named fragments, multiple fieldNodes, and missing selectionSet; fix reversed comment.

Current use of info.fieldNodes[0].selectionSet fails with named fragments, multiple fieldNodes, or undefined selectionSet. Flatten all fieldNodes and inline named/inline fragments before containment. Also fix the comment to reflect the actual check (available contains required).

Apply this diff:

-            // find the best resolver by diffing the selection sets
-            const availableSelSet = info.fieldNodes[0].selectionSet;
+            // find the best resolver by diffing the selection sets
+            if (!info.fieldNodes?.length) {
+              return resolvePayload(payload);
+            }
+            function inlineFragmentSpreads(
+              selSet: SelectionSetNode,
+              info: GraphQLResolveInfo,
+            ): SelectionSetNode {
+              const selections: any[] = [];
+              for (const sel of selSet.selections) {
+                if (sel.kind === Kind.FRAGMENT_SPREAD) {
+                  const frag = info.fragments?.[sel.name.value];
+                  if (frag) {
+                    selections.push(
+                      ...inlineFragmentSpreads(frag.selectionSet, info).selections,
+                    );
+                  }
+                  continue;
+                }
+                if (sel.kind === Kind.INLINE_FRAGMENT) {
+                  selections.push({
+                    ...sel,
+                    selectionSet: inlineFragmentSpreads(sel.selectionSet, info),
+                  });
+                  continue;
+                }
+                selections.push(sel);
+              }
+              return { kind: Kind.SELECTION_SET, selections };
+            }
+            const availableSelSet = inlineFragmentSpreads(
+              {
+                kind: Kind.SELECTION_SET,
+                selections: info.fieldNodes.flatMap(n => n.selectionSet?.selections ?? []),
+              },
+              info,
+            );
             let resolver: MergedTypeResolver | null = null;
             let subschema: Subschema | null = null;
             for (const [requiredSubschema, requiredSelSet] of mergedTypeInfo.selectionSets) {
               const matchResolver = mergedTypeInfo?.resolvers.get(requiredSubschema);
               if (!matchResolver) {
                 // the subschema has no resolvers, nothing to search for
                 continue;
               }
-              if (containsSelectionSet(requiredSelSet, availableSelSet)) {
-                // all of the fields of the requesting selection set is exist in the required selection set
+              if (containsSelectionSet(requiredSelSet, availableSelSet)) {
+                // all required fields exist in the available selection set
                 resolver = matchResolver;
                 subschema = requiredSubschema;
                 break;
               }
             }
🧹 Nitpick comments (1)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)

244-261: Guard and fallback if no match or empty selections.

You already fallback when no resolver/subschema found. Add a quick guard to skip invoking resolver when availableSelSet has no selections after flattening (possible with directives that prune fields).

-            return handleMaybePromise(
+            if (!availableSelSet?.selections?.length) {
+              return resolvePayload(payload);
+            }
+            return handleMaybePromise(
               () =>
                 resolver(
                   payload,
                   ctx,
                   info,
                   subschema,
                   availableSelSet,
                   undefined,
                   info.returnType,
                 ),
               resolvePayload,
             );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6e5869f and 2425fda.

📒 Files selected for processing (2)
  • packages/legacy/utils/src/containsSelectionSet.ts (1 hunks)
  • packages/legacy/utils/src/resolve-additional-resolvers.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/legacy/utils/src/containsSelectionSet.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T17:11:35.160Z
Learnt from: enisdenjo
PR: ardatan/graphql-mesh#8716
File: packages/legacy/types/src/pubsub.ts:64-79
Timestamp: 2025-08-22T17:11:35.160Z
Learning: In the GraphQL Mesh codebase, when implementing MeshPubSub interfaces, the optional `options` parameter in the subscribe method may be intentionally omitted if it's unused and irrelevant for the specific implementation, even if it causes a minor TypeScript interface mismatch.

Applied to files:

  • packages/legacy/utils/src/resolve-additional-resolvers.ts
🧬 Code graph analysis (1)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)
packages/legacy/utils/src/containsSelectionSet.ts (1)
  • containsSelectionSet (7-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: e2e / node v20
  • GitHub Check: e2e / node v22
  • GitHub Check: e2e / node v18
  • GitHub Check: unit / node 18
  • GitHub Check: deployment
🔇 Additional comments (3)
packages/legacy/utils/src/resolve-additional-resolvers.ts (3)

23-23: Type-only import: good change.

Reduces bundle/RHS side-effects from @graphql-tools/delegate.


209-214: Rename to resolvePayload: fine.

Behavior preserved; improves readability.


215-226: Graceful stitching fallback: good.

Early returns for missing stitchingInfo/mergedType are correct for the new “auto-pick or passthrough” behavior.

Copy link
Contributor

@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: 1

♻️ Duplicate comments (1)
packages/legacy/utils/test/containsSelectionSet.spec.ts (1)

65-83: Enable coverage for untyped inline fragments and repeated-field merging.

These TODOs are important for the auto-detection logic that resolves missing fields from subgraphs. Please enable them and align the implementation accordingly. Note: similar feedback was left previously.

Apply this diff to turn the TODOs into active test cases:

-  // TODO: inline fragments without type conditions can be flattened
-  // {
-  //   requiredSelSet: `{ ... { id name } }`,
-  //   selSet: `{ id name }`,
-  //   result: true,
-  // },
-  // {
-  //   requiredSelSet: `{ id name }`,
-  //   selSet: `{ ... { id name } }`,
-  //   result: true,
-  // },
-  // TODO: merged selection should satisfy requirement
-  // {
-  //   requiredSelSet: `{ user { a b } }`,
-  //   selSet: `{ user { a } user { b } }`,
-  //   result: true,
-  // },
+  // Untyped inline fragments can be flattened in either direction
+  {
+    requiredSelSet: `{ ... { id name } }`,
+    selSet: `{ id name }`,
+    result: true,
+  },
+  {
+    requiredSelSet: `{ id name }`,
+    selSet: `{ ... { id name } }`,
+    result: true,
+  },
+  // Repeated fields should merge their sub-selections
+  {
+    requiredSelSet: `{ user { a b } }`,
+    selSet: `{ user { a } user { b } }`,
+    result: true,
+  },

To make these pass, the current implementation in packages/legacy/utils/src/containsSelectionSet.ts needs to:

  • Flatten untyped inline fragments (no typeCondition) before comparison.
  • Merge repeated fields (and their nested selection sets) and merge typed inline fragments with the same type condition.
    Here’s a minimal approach you can adapt:
// packages/legacy/utils/src/containsSelectionSet.ts
import { Kind, type FieldNode, type InlineFragmentNode, type SelectionNode, type SelectionSetNode } from 'graphql';

function responseKey(f: FieldNode) {
  return f.alias?.value ?? f.name.value;
}

function normalize(selSet: SelectionSetNode): SelectionSetNode {
  const fieldMap = new Map<string, FieldNode>();
  const typedFragMap = new Map<string, InlineFragmentNode>();

  const pushField = (f: FieldNode) => {
    const key = responseKey(f);
    const existing = fieldMap.get(key);
    if (!existing) {
      fieldMap.set(key, f.selectionSet ? { ...f, selectionSet: normalize(f.selectionSet) } : f);
      return;
    }
    // merge selection sets if present
    if (f.selectionSet || existing.selectionSet) {
      const a = existing.selectionSet ?? { kind: Kind.SELECTION_SET, selections: [] as SelectionNode[] };
      const b = f.selectionSet ? normalize(f.selectionSet) : { kind: Kind.SELECTION_SET, selections: [] as SelectionNode[] };
      fieldMap.set(key, { ...existing, selectionSet: mergeSelectionSets(a, b) });
    }
  };

  const pushTypedFrag = (frag: InlineFragmentNode) => {
    const typeName = frag.typeCondition!.name.value;
    const existing = typedFragMap.get(typeName);
    const normalized = { ...frag, selectionSet: normalize(frag.selectionSet) };
    if (!existing) {
      typedFragMap.set(typeName, normalized);
      return;
    }
    typedFragMap.set(typeName, {
      ...existing,
      selectionSet: mergeSelectionSets(existing.selectionSet, normalized.selectionSet),
    });
  };

  for (const sel of selSet.selections) {
    if (sel.kind === Kind.FIELD) {
      pushField(sel);
    } else if (sel.kind === Kind.INLINE_FRAGMENT) {
      if (sel.typeCondition) {
        pushTypedFrag(sel);
      } else {
        // untyped inline fragment: flatten
        const inner = normalize(sel.selectionSet);
        for (const innerSel of inner.selections) {
          if (innerSel.kind === Kind.FIELD) pushField(innerSel);
          else if (innerSel.kind === Kind.INLINE_FRAGMENT && innerSel.typeCondition) pushTypedFrag(innerSel);
        }
      }
    }
    // FRAGMENT_SPREAD intentionally unsupported here
  }

  return {
    kind: Kind.SELECTION_SET,
    selections: [
      ...[...fieldMap.values()],
      ...[...typedFragMap.values()],
    ],
  };
}

function mergeSelectionSets(a: SelectionSetNode, b: SelectionSetNode): SelectionSetNode {
  return normalize({ kind: Kind.SELECTION_SET, selections: [...a.selections, ...b.selections] });
}

export function containsSelectionSet(requiredSelSet: SelectionSetNode, selSet: SelectionSetNode): boolean {
  const req = normalize(requiredSelSet);
  const have = normalize(selSet);

  ReqLoop: for (const reqSel of req.selections) {
    switch (reqSel.kind) {
      case Kind.FIELD: {
        for (const sel of have.selections) {
          if (sel.kind === Kind.FIELD && responseKey(sel) === responseKey(reqSel)) {
            if (!reqSel.selectionSet && !sel.selectionSet) continue ReqLoop;
            if (!!reqSel.selectionSet !== !!sel.selectionSet) return false;
            if (containsSelectionSet(reqSel.selectionSet!, sel.selectionSet!)) continue ReqLoop;
          }
        }
        return false;
      }
      case Kind.INLINE_FRAGMENT: {
        for (const sel of have.selections) {
          if (sel.kind !== Kind.INLINE_FRAGMENT) continue;
          if (!!sel.typeCondition !== !!reqSel.typeCondition) continue;
          if (!sel.typeCondition && !reqSel.typeCondition) {
            if (containsSelectionSet(reqSel.selectionSet, sel.selectionSet)) continue ReqLoop;
          } else if (sel.typeCondition!.name.value === reqSel.typeCondition!.name.value) {
            if (containsSelectionSet(reqSel.selectionSet, sel.selectionSet)) continue ReqLoop;
          }
        }
        return false;
      }
      default:
        return false;
    }
  }
  return true;
}
🧹 Nitpick comments (1)
packages/legacy/utils/test/containsSelectionSet.spec.ts (1)

56-64: Typed inline-fragment cases look correct. Add an order-invariance assertion.

Add a quick guard that field order doesn’t matter during containment.

Apply this diff inside the existing matrix:

   {
     requiredSelSet: `{ ... on Person { first } }`,
     selSet: `{ ... on Animal { first } }`,
     result: false,
   },
+  // Order of fields should not matter
+  {
+    requiredSelSet: `{ name id }`,
+    selSet: `{ id name }`,
+    result: true,
+  },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2425fda and 67e5d1d.

📒 Files selected for processing (3)
  • e2e/subscriptions-type-merging/services/products.ts (2 hunks)
  • packages/legacy/utils/src/containsSelectionSet.ts (1 hunks)
  • packages/legacy/utils/test/containsSelectionSet.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/legacy/utils/src/containsSelectionSet.ts
  • e2e/subscriptions-type-merging/services/products.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/legacy/utils/test/containsSelectionSet.spec.ts (1)
packages/legacy/utils/src/containsSelectionSet.ts (1)
  • containsSelectionSet (7-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: release / snapshot
  • GitHub Check: deployment
  • GitHub Check: e2e / node v22
  • GitHub Check: e2e / node v18
  • GitHub Check: e2e / node v20
  • GitHub Check: integration / node 18
  • GitHub Check: integration / node 20
  • GitHub Check: apollo-federation-compatibility
  • GitHub Check: integration / node 22
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: check
  • GitHub Check: unit / node 18
🔇 Additional comments (1)
packages/legacy/utils/test/containsSelectionSet.spec.ts (1)

4-39: Solid baseline coverage for fields and nested selections.

These cases exercise subset/superset checks and object-vs-scalar mismatches correctly.

Copy link
Contributor

@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: 2

♻️ Duplicate comments (1)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)

256-256: Potential issue with accessing fieldNodes[0].selectionSet

Based on the past review comments, accessing info.fieldNodes[0].selectionSet without guards could cause runtime errors if:

  • fieldNodes array is empty
  • selectionSet is undefined

Add safety checks before accessing the selection set:

+            if (!info.fieldNodes?.length || !info.fieldNodes[0].selectionSet) {
+              return resolvePayload(payload);
+            }
             return handleMaybePromise(
               () =>
                 resolver(
                   payload,
                   ctx,
                   info,
                   subschema,
                   info.fieldNodes[0].selectionSet,
                   undefined,
                   info.returnType,
                 ),
               resolvePayload,
             );
🧹 Nitpick comments (3)
packages/legacy/utils/test/selectionSet.spec.ts (1)

65-87: Consider implementing the TODO test cases for completeness

The commented-out test cases for fragment definitions/spreads and merged selections would provide valuable coverage for edge cases. These scenarios could be encountered in real-world usage.

Would you like me to help implement the missing test cases for fragment definitions, spreads, and merged selections? I can open an issue to track this enhancement.

packages/legacy/utils/test/resolve-additional-resolvers.spec.ts (1)

84-86: Consider using a more deterministic approach for async timing

Using setTimeout with 0ms delay could potentially lead to flaky tests in certain environments. Consider using a more explicit async pattern.

-  setTimeout(() => {
-    pubsub.publish('new_product', { id: '60' });
-  }, 0);
+  await new Promise(resolve => setImmediate(resolve));
+  pubsub.publish('new_product', { id: '60' });

Or alternatively, publish synchronously since the subscription is already set up:

-  setTimeout(() => {
-    pubsub.publish('new_product', { id: '60' });
-  }, 0);
+  pubsub.publish('new_product', { id: '60' });
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)

238-239: Inaccurate comment about field comparison

The comment states "all of the fields of the requesting selection set is exist in the required selection set" but the logic is inverted - it checks if all fields in the required selection set exist in the available selection set.

-              // all of the fields of the requesting selection set is exist in the required selection set
+              // all fields of the required selection set exist in the available selection set
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 67e5d1d and 8a20760.

📒 Files selected for processing (5)
  • .changeset/puny-geckos-attack.md (1 hunks)
  • packages/legacy/utils/src/resolve-additional-resolvers.ts (4 hunks)
  • packages/legacy/utils/src/selectionSet.ts (1 hunks)
  • packages/legacy/utils/test/resolve-additional-resolvers.spec.ts (1 hunks)
  • packages/legacy/utils/test/selectionSet.spec.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/puny-geckos-attack.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T17:11:35.160Z
Learnt from: enisdenjo
PR: ardatan/graphql-mesh#8716
File: packages/legacy/types/src/pubsub.ts:64-79
Timestamp: 2025-08-22T17:11:35.160Z
Learning: In the GraphQL Mesh codebase, when implementing MeshPubSub interfaces, the optional `options` parameter in the subscribe method may be intentionally omitted if it's unused and irrelevant for the specific implementation, even if it causes a minor TypeScript interface mismatch.

Applied to files:

  • packages/legacy/utils/test/resolve-additional-resolvers.spec.ts
  • packages/legacy/utils/src/resolve-additional-resolvers.ts
🧬 Code graph analysis (3)
packages/legacy/utils/test/selectionSet.spec.ts (1)
packages/legacy/utils/src/selectionSet.ts (1)
  • containsSelectionSet (7-72)
packages/legacy/utils/test/resolve-additional-resolvers.spec.ts (1)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)
  • resolveAdditionalResolversWithoutImport (159-373)
packages/legacy/utils/src/resolve-additional-resolvers.ts (1)
packages/legacy/utils/src/selectionSet.ts (2)
  • selectionSetOfData (74-95)
  • containsSelectionSet (7-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: deployment
  • GitHub Check: apollo-federation-compatibility
  • GitHub Check: integration / node 22
  • GitHub Check: e2e / node v22
  • GitHub Check: integration / node 20
  • GitHub Check: e2e / node v18
  • GitHub Check: unit / node 22
  • GitHub Check: integration / node 18
  • GitHub Check: e2e / node v20
  • GitHub Check: unit / node 18
  • GitHub Check: unit / node 20
  • GitHub Check: check
  • GitHub Check: release / snapshot
🔇 Additional comments (6)
packages/legacy/utils/test/selectionSet.spec.ts (1)

1-95: Comprehensive test coverage for containsSelectionSet function

The test suite provides good coverage for the containsSelectionSet function with well-structured test cases covering various scenarios including scalar fields, nested objects, and inline fragments. The parameterized testing approach is clean and maintainable.

packages/legacy/utils/test/resolve-additional-resolvers.spec.ts (1)

9-101: Well-structured integration test for subscription field resolution

The test effectively validates the auto-detection mechanism by:

  1. Setting up a products schema with merge configuration
  2. Creating a subscription without explicit sourceName
  3. Publishing an event with only the id field
  4. Verifying that additional fields (name, price) are correctly resolved from the subgraph

This demonstrates the core functionality of the PR objectives.

packages/legacy/utils/src/selectionSet.ts (2)

7-72: Well-implemented selection set containment checker with clear logic

The containsSelectionSet function correctly handles:

  • Scalar vs object field type matching
  • Recursive validation for nested selections
  • Inline fragments with and without type conditions
  • Proper use of labeled loops for control flow

The implementation is robust and handles edge cases appropriately.


74-95: Efficient implementation of selectionSetOfData

The function correctly builds a SelectionSetNode from a data object, properly handling both scalar and nested object values.

packages/legacy/utils/src/resolve-additional-resolvers.ts (2)

180-180: Good defensive programming with optional chaining

Using context?.pubsub prevents potential runtime errors when context is undefined, aligning with the changeset note about handling undefined contexts.


208-262: Excellent implementation of auto-detection logic with proper fallbacks

The resolver correctly:

  1. Checks for stitching info availability
  2. Validates merged type information exists
  3. Finds the best matching resolver by comparing selection sets
  4. Falls back gracefully to the payload when no suitable resolver is found

This implementation successfully achieves the PR objective of automatically finding the appropriate subgraph.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (4)
packages/legacy/utils/test/selectionSet.spec.ts (4)

5-65: Add alias/arguments/directives and fragment-spread cases to lock intended semantics

These are supported/ignored by the current matcher and worth pinning with tests. Insert before Line 66.

@@
   {
     requiredSelSet: `{ id person { last friend { first } } }`,
     selSet: `{ id person { last friend { first } } }`,
     result: true,
   },
+  // alias should not affect containment (match by field name)
+  {
+    requiredSelSet: `{ productId }`,
+    selSet: `{ id: productId }`,
+    result: true,
+  },
+  {
+    requiredSelSet: `{ id: productId }`,
+    selSet: `{ productId }`,
+    result: true,
+  },
+  // field arguments are intentionally ignored by the matcher
+  {
+    requiredSelSet: `{ user(id: 1) { id } }`,
+    selSet: `{ user(id: 2) { id } }`,
+    result: true,
+  },
+  // directives are intentionally ignored by the matcher
+  {
+    requiredSelSet: `{ user @include(if: true) { id } }`,
+    selSet: `{ user { id } }`,
+    result: true,
+  },
+  // fragment spreads are not supported by the matcher yet
+  {
+    requiredSelSet: `{ ...PersonFields }`,
+    selSet: `{ id }`,
+    result: false,
+  },
+  {
+    requiredSelSet: `{ id }`,
+    selSet: `{ ...PersonFields }`,
+    result: false,
+  },

57-65: Inline fragment tests look good; consider mixed-level assertions

Optional: add a case where required has inline fragment plus sibling fields to assert partial satisfaction rules.


66-88: Turn TODOs into actionable, non-failing placeholders

Use test.todo to track future behavior without breaking CI.

@@
   // TODO: fragment defs and spreads
   // TODO: inline fragments without type conditions can be flattened
+  it.todo('containsSelectionSet: supports fragment definitions and spreads');
+  it.todo('containsSelectionSet: flattens inline fragments without type conditions');
@@
   // TODO: merged selection should satisfy requirement
+  it.todo('containsSelectionSet: merges repeated selections of the same field (union of subselections)');

98-133: Add edge-case tests for selectionSetOfData (empty arrays, scalars-in-arrays, nulls, mixed object arrays)

These solidify assumptions used by auto-detection from payloads.

@@
 it('should create the correct selection set of data', () => {
   const data = {
@@
 });
+
+it('should handle arrays, empty arrays, nulls, and use the first object shape in arrays', () => {
+  const data = {
+    tags: [],
+    nums: [1, 2, 3],
+    meta: null,
+    items: [
+      { id: 'a', value: 1 },
+      { id: 'b', extra: true },
+    ],
+  };
+  const selSet = selectionSetOfData(data);
+  expect(print(selSet)).toMatchInlineSnapshot(`
+"{
+  tags
+  nums
+  meta
+  items {
+    id
+    value
+  }
+}"
+`);
+});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 87360c6 and 2edabf4.

📒 Files selected for processing (2)
  • packages/legacy/utils/src/selectionSet.ts (1 hunks)
  • packages/legacy/utils/test/selectionSet.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/legacy/utils/src/selectionSet.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/legacy/utils/test/selectionSet.spec.ts (1)
packages/legacy/utils/src/selectionSet.ts (2)
  • containsSelectionSet (7-72)
  • selectionSetOfData (74-109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: e2e / node v18
  • GitHub Check: integration / node 18
  • GitHub Check: unit / node 22
  • GitHub Check: e2e / node v20
  • GitHub Check: e2e / node v22
  • GitHub Check: check
  • GitHub Check: release / snapshot
  • GitHub Check: deployment
🔇 Additional comments (1)
packages/legacy/utils/test/selectionSet.spec.ts (1)

5-55: Good coverage of base and nested cases

Matrix includes exact, superset/subset, and nested field checks and aligns with current containsSelectionSet semantics.

enisdenjo added a commit to graphql-hive/console that referenced this pull request Aug 29, 2025
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