Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions router/core/factoryresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type InstanceData struct {
}

type FactoryResolver interface {
ResolveGraphqlFactory(subgraphName string) (plan.PlannerFactory[graphql_datasource.Configuration], error)
ResolveGraphqlFactory(subgraphName string, hasSubscriptions bool) (plan.PlannerFactory[graphql_datasource.Configuration], error)
ResolveStaticFactory() (plan.PlannerFactory[staticdatasource.Configuration], error)
InstanceData() InstanceData
}
Expand Down Expand Up @@ -167,7 +167,18 @@ func NewDefaultFactoryResolver(
}
}

func (d *DefaultFactoryResolver) ResolveGraphqlFactory(subgraphName string) (plan.PlannerFactory[graphql_datasource.Configuration], error) {
// hasSubscriptionRootNodes reports whether a data source's root nodes include
// any Subscription fields.
func hasSubscriptionRootNodes(rootNodes []*nodev1.TypeField) bool {
for _, node := range rootNodes {
if node.TypeName == "Subscription" && len(node.FieldNames) > 0 {
return true
}
}
return false
Comment on lines +172 to +178

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant files and inspect the resolver helper and its callers.
git ls-files | rg '(^|/)(factoryresolver\.go|.*engine.*|.*schema.*|.*graphql.*|.*nodev1.*)\.go$' || true

printf '\n--- factoryresolver outline ---\n'
ast-grep outline router/core/factoryresolver.go --view expanded || true

printf '\n--- search for hasSubscriptionRootNodes and hasSubscriptions ---\n'
rg -n "hasSubscriptionRootNodes|hasSubscriptions|ResolveGraphqlFactory|Subscription" router/core -g '*.go' || true

printf '\n--- inspect relevant slices of factoryresolver.go ---\n'
sed -n '1,260p' router/core/factoryresolver.go

printf '\n--- search for RootNodes generation / normalization ---\n'
rg -n "RootNodes|TypeField|subscription:|Subscription" . -g '*.go' || true

Repository: wundergraph/cosmo

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- factoryresolver.go relevant lines ---\n'
sed -n '160,220p' router/core/factoryresolver.go | cat -n

printf '\n--- root-node generation / schema metadata path ---\n'
rg -n "RootNodes|TypeName:|FieldNames:|subscription:" router -g '*.go' | head -n 200

printf '\n--- inspect any schema-to-root-node conversion helpers ---\n'
rg -n "TypeField|RootNodes" router/core router -g '*.go' | head -n 200

Repository: wundergraph/cosmo

Length of output: 41770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- schemaloader loader root-node construction ---\n'
sed -n '150,240p' router/pkg/schemaloader/loader.go | cat -n

printf '\n--- inspect router config client root-node examples ---\n'
sed -n '1,140p' router/pkg/routerconfig/client.go | cat -n

printf '\n--- search for schema root type handling in router/pkg/schemaloader and router/core ---\n'
rg -n "subscription:|query:|mutation:|RootNodes|TypeName.*Subscription|TypeName.*Query|TypeName.*Mutation" router/pkg/schemaloader router/core -g '*.go'

Repository: wundergraph/cosmo

Length of output: 9624


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- schemaloader loader.go around root-node usage ---\n'
sed -n '160,230p' router/pkg/schemaloader/loader.go | cat -n

printf '\n--- schemaloader tests with subscription schemas ---\n'
rg -n "subscription:" router/pkg/schemaloader/loader_test.go router/pkg/schemaloader -g '*.go' -A 6 -B 6

printf '\n--- search for custom subscription root names in generated config/tests ---\n'
rg -n 'TypeName:\s*"Subscription"|subscription:\s*[A-Za-z_][A-Za-z0-9_]*' router -g '*.go' -A 4 -B 4

Repository: wundergraph/cosmo

Length of output: 3242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- production code that writes DataSourceConfiguration.RootNodes ---\n'
rg -n "RootNodes:\s*\[\]\*nodev1.TypeField|RootNodes:\s*\[\]plan.TypeField|RootNodes:\s*make\(" router -g '*.go' -A 4 -B 4

printf '\n--- inspect surrounding code in likely builders ---\n'
sed -n '1,220p' router/pkg/routerconfig/client.go | cat -n
sed -n '560,660p' router/core/factoryresolver.go | cat -n

Repository: wundergraph/cosmo

Length of output: 39251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- search for custom operation root types in schemas/configs/tests ---\n'
rg -n 'schema\s*\{[^}]*subscription:\s*[A-Za-z_][A-Za-z0-9_]*|schema\s*\{[^}]*query:\s*[A-Za-z_][A-Za-z0-9_]*|schema\s*\{[^}]*mutation:\s*[A-Za-z_][A-Za-z0-9_]*' router -g '*.go' -g '*.graphql' -g '*.gql' -A 3 -B 3

printf '\n--- search for explicit EventStream or non-Subscription root type names ---\n'
rg -n 'EventStream|type\s+Subscription\b|subscription:\s*[A-Za-z_][A-Za-z0-9_]*' router -g '*.go' -g '*.graphql' -g '*.gql' -A 3 -B 3

Repository: wundergraph/cosmo

Length of output: 5781


Detect the configured subscription root type, not a hard-coded Subscription. dataSourceMetaData copies TypeName verbatim, so a valid schema like schema { subscription: EventStream } will be treated as subscription-free and the planner will skip the subscription client. Add a case for a custom subscription root type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/core/factoryresolver.go` around lines 172 - 178, Update
hasSubscriptionRootNodes to recognize the configured subscription root type in
addition to the default "Subscription". Use the schema/root-type configuration
available to the resolver when comparing node.TypeName, so custom declarations
such as EventStream are detected and subscription planning remains enabled.

}

func (d *DefaultFactoryResolver) ResolveGraphqlFactory(subgraphName string, hasSubscriptions bool) (plan.PlannerFactory[graphql_datasource.Configuration], error) {
if d.connector != nil {
// If the connector is not nil, we try to get the provider for the subgraph.
// In case of a provider, we use the gRPC client provider to create the factory.
Expand All @@ -183,9 +194,13 @@ func (d *DefaultFactoryResolver) ResolveGraphqlFactory(subgraphName string) (pla

if d.transportFactory == nil || d.baseTransport == nil {
// dummy implementation for plan generator that doesn't make requests
subscriptionClient := graphql_datasource.NewGraphQLSubscriptionClient(d.engineCtx,
d.subscriptionClientOptions...,
)
var subscriptionClient graphql_datasource.GraphQLSubscriptionClient
if hasSubscriptions {
subscriptionClient = graphql_datasource.NewGraphQLSubscriptionClient(d.engineCtx,
d.subscriptionClientOptions...,
)
}

return graphql_datasource.NewFactory(d.engineCtx, http.DefaultClient, subscriptionClient)
}

Expand All @@ -194,14 +209,17 @@ func (d *DefaultFactoryResolver) ResolveGraphqlFactory(subgraphName string) (pla
Transport: d.transportFactory.RoundTripper(d.baseTransport),
}

streamingClient := &http.Client{
Transport: d.transportFactory.RoundTripper(d.baseTransport),
}
var subscriptionClient graphql_datasource.GraphQLSubscriptionClient
if hasSubscriptions {
streamingClient := &http.Client{
Transport: d.transportFactory.RoundTripper(d.baseTransport),
}

subscriptionClient := graphql_datasource.NewGraphQLSubscriptionClient(
d.engineCtx,
append([]graphql_datasource.SubscriptionClientOption{graphql_datasource.WithUpgradeClient(defaultHTTPClient), graphql_datasource.WithStreamingClient(streamingClient)}, d.subscriptionClientOptions...)...,
)
subscriptionClient = graphql_datasource.NewGraphQLSubscriptionClient(
d.engineCtx,
append([]graphql_datasource.SubscriptionClientOption{graphql_datasource.WithUpgradeClient(defaultHTTPClient), graphql_datasource.WithStreamingClient(streamingClient)}, d.subscriptionClientOptions...)...,
)
}

if subgraphClient, ok := d.subgraphHTTPClients[subgraphName]; ok {
// it's intentional that we're not using the subgraphClient for subscriptions
Expand Down Expand Up @@ -355,7 +373,7 @@ func (l *Loader) Load(engineConfig *nodev1.EngineConfiguration, subgraphs []*nod
return nil, providers, err
}

out, err = plan.NewDataSourceConfiguration[staticdatasource.Configuration](
out, err = plan.NewDataSourceConfiguration(
in.Id,
factory,
l.dataSourceMetaData(in),
Expand Down Expand Up @@ -483,12 +501,12 @@ func (l *Loader) Load(engineConfig *nodev1.EngineConfiguration, subgraphs []*nod

dataSourceName := l.subgraphName(subgraphs, in.Id)

factory, err := l.resolver.ResolveGraphqlFactory(dataSourceName)
factory, err := l.resolver.ResolveGraphqlFactory(dataSourceName, hasSubscriptionRootNodes(in.RootNodes))
if err != nil {
return nil, providers, err
}

out, err = plan.NewDataSourceConfigurationWithName[graphql_datasource.Configuration](
out, err = plan.NewDataSourceConfigurationWithName(
in.Id,
dataSourceName,
factory,
Expand Down
Loading