-
Notifications
You must be signed in to change notification settings - Fork 349
Auto merge types from subscriptions in additional type defs #8736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughImplements automatic subgraph detection for subscription-resolved fields by removing the need for Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Apollo Federation Subgraph Compatibility Results
Learn more: |
🚀 Snapshot Release (
|
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 ↗︎ |
There was a problem hiding this 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 “thesourceName
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
(andrequiredSelSet
) 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.
📒 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
orname
) requested by clients; otherwise__resolveReference
may fabricate placeholder data.If needed, update the publisher to include
id
orname
, and add an e2e assertion that a client selecting onlyid
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.
There was a problem hiding this 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.
📒 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.
There was a problem hiding this 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.
📒 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.
There was a problem hiding this 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].selectionSetBased on the past review comments, accessing
info.fieldNodes[0].selectionSet
without guards could cause runtime errors if:
fieldNodes
array is emptyselectionSet
is undefinedAdd 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 completenessThe 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 timingUsing
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 comparisonThe 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.
📒 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 functionThe 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 resolutionThe test effectively validates the auto-detection mechanism by:
- Setting up a products schema with merge configuration
- Creating a subscription without explicit sourceName
- Publishing an event with only the id field
- 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 logicThe 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 selectionSetOfDataThe 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 chainingUsing
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 fallbacksThe resolver correctly:
- Checks for stitching info availability
- Validates merged type information exists
- Finds the best matching resolver by comparing selection sets
- 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.
This reverts commit 68f0190.
There was a problem hiding this 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 semanticsThese 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 assertionsOptional: add a case where required has inline fragment plus sibling fields to assert partial satisfaction rules.
66-88
: Turn TODOs into actionable, non-failing placeholdersUse 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.
📒 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 casesMatrix includes exact, superset/subset, and nested field checks and aligns with current containsSelectionSet semantics.
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" ) } ` })