graphql-stitching 1.2.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -17
- data/docs/README.md +2 -1
- data/docs/mechanics.md +2 -1
- data/docs/resolver.md +101 -0
- data/lib/graphql/stitching/client.rb +5 -1
- data/lib/graphql/stitching/composer/{boundary_config.rb → resolver_config.rb} +18 -13
- data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
- data/lib/graphql/stitching/composer/validate_resolvers.rb +97 -0
- data/lib/graphql/stitching/composer.rb +107 -112
- data/lib/graphql/stitching/executor/{boundary_source.rb → resolver_source.rb} +40 -32
- data/lib/graphql/stitching/executor.rb +3 -3
- data/lib/graphql/stitching/plan.rb +3 -4
- data/lib/graphql/stitching/planner.rb +30 -41
- data/lib/graphql/stitching/planner_step.rb +6 -6
- data/lib/graphql/stitching/resolver/arguments.rb +284 -0
- data/lib/graphql/stitching/resolver/keys.rb +206 -0
- data/lib/graphql/stitching/resolver.rb +70 -0
- data/lib/graphql/stitching/shaper.rb +3 -3
- data/lib/graphql/stitching/skip_include.rb +1 -1
- data/lib/graphql/stitching/supergraph/key_directive.rb +13 -0
- data/lib/graphql/stitching/supergraph/resolver_directive.rb +4 -4
- data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
- data/lib/graphql/stitching/supergraph.rb +31 -144
- data/lib/graphql/stitching/util.rb +28 -0
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +3 -2
- metadata +11 -7
- data/lib/graphql/stitching/boundary.rb +0 -29
- data/lib/graphql/stitching/composer/validate_boundaries.rb +0 -96
- data/lib/graphql/stitching/export_selection.rb +0 -42
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-stitching
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg MacWilliam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- docs/images/stitching.png
|
90
90
|
- docs/mechanics.md
|
91
91
|
- docs/request.md
|
92
|
+
- docs/resolver.md
|
92
93
|
- docs/supergraph.md
|
93
94
|
- examples/file_uploads/Gemfile
|
94
95
|
- examples/file_uploads/Procfile
|
@@ -110,27 +111,30 @@ files:
|
|
110
111
|
- gemfiles/graphql_2.2.0.gemfile
|
111
112
|
- graphql-stitching.gemspec
|
112
113
|
- lib/graphql/stitching.rb
|
113
|
-
- lib/graphql/stitching/boundary.rb
|
114
114
|
- lib/graphql/stitching/client.rb
|
115
115
|
- lib/graphql/stitching/composer.rb
|
116
116
|
- lib/graphql/stitching/composer/base_validator.rb
|
117
|
-
- lib/graphql/stitching/composer/
|
118
|
-
- lib/graphql/stitching/composer/validate_boundaries.rb
|
117
|
+
- lib/graphql/stitching/composer/resolver_config.rb
|
119
118
|
- lib/graphql/stitching/composer/validate_interfaces.rb
|
119
|
+
- lib/graphql/stitching/composer/validate_resolvers.rb
|
120
120
|
- lib/graphql/stitching/executor.rb
|
121
|
-
- lib/graphql/stitching/executor/
|
121
|
+
- lib/graphql/stitching/executor/resolver_source.rb
|
122
122
|
- lib/graphql/stitching/executor/root_source.rb
|
123
|
-
- lib/graphql/stitching/export_selection.rb
|
124
123
|
- lib/graphql/stitching/http_executable.rb
|
125
124
|
- lib/graphql/stitching/plan.rb
|
126
125
|
- lib/graphql/stitching/planner.rb
|
127
126
|
- lib/graphql/stitching/planner_step.rb
|
128
127
|
- lib/graphql/stitching/request.rb
|
128
|
+
- lib/graphql/stitching/resolver.rb
|
129
|
+
- lib/graphql/stitching/resolver/arguments.rb
|
130
|
+
- lib/graphql/stitching/resolver/keys.rb
|
129
131
|
- lib/graphql/stitching/shaper.rb
|
130
132
|
- lib/graphql/stitching/skip_include.rb
|
131
133
|
- lib/graphql/stitching/supergraph.rb
|
134
|
+
- lib/graphql/stitching/supergraph/key_directive.rb
|
132
135
|
- lib/graphql/stitching/supergraph/resolver_directive.rb
|
133
136
|
- lib/graphql/stitching/supergraph/source_directive.rb
|
137
|
+
- lib/graphql/stitching/supergraph/to_definition.rb
|
134
138
|
- lib/graphql/stitching/util.rb
|
135
139
|
- lib/graphql/stitching/version.rb
|
136
140
|
homepage: https://github.com/gmac/graphql-stitching-ruby
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module Stitching
|
5
|
-
# Defines a boundary query that provides direct access to an entity type.
|
6
|
-
Boundary = Struct.new(
|
7
|
-
:location,
|
8
|
-
:type_name,
|
9
|
-
:key,
|
10
|
-
:field,
|
11
|
-
:arg,
|
12
|
-
:list,
|
13
|
-
:federation,
|
14
|
-
keyword_init: true
|
15
|
-
) do
|
16
|
-
def as_json
|
17
|
-
{
|
18
|
-
location: location,
|
19
|
-
type_name: type_name,
|
20
|
-
key: key,
|
21
|
-
field: field,
|
22
|
-
arg: arg,
|
23
|
-
list: list,
|
24
|
-
federation: federation,
|
25
|
-
}.tap(&:compact!)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL::Stitching
|
4
|
-
class Composer
|
5
|
-
class ValidateBoundaries < BaseValidator
|
6
|
-
|
7
|
-
def perform(supergraph, composer)
|
8
|
-
supergraph.schema.types.each do |type_name, type|
|
9
|
-
# objects and interfaces that are not the root operation types
|
10
|
-
next unless type.kind.object? || type.kind.interface?
|
11
|
-
next if supergraph.schema.query == type || supergraph.schema.mutation == type
|
12
|
-
next if type.graphql_name.start_with?("__")
|
13
|
-
|
14
|
-
# multiple subschemas implement the type
|
15
|
-
candidate_types_by_location = composer.candidate_types_by_name_and_location[type_name]
|
16
|
-
next unless candidate_types_by_location.length > 1
|
17
|
-
|
18
|
-
boundaries = supergraph.boundaries[type_name]
|
19
|
-
if boundaries&.any?
|
20
|
-
validate_as_boundary(supergraph, type, candidate_types_by_location, boundaries)
|
21
|
-
elsif type.kind.object?
|
22
|
-
validate_as_shared(supergraph, type, candidate_types_by_location)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def validate_as_boundary(supergraph, type, candidate_types_by_location, boundaries)
|
30
|
-
# abstract boundaries are expanded with their concrete implementations, which each get validated. Ignore the abstract itself.
|
31
|
-
return if type.kind.abstract?
|
32
|
-
|
33
|
-
# only one boundary allowed per type/location/key
|
34
|
-
boundaries_by_location_and_key = boundaries.each_with_object({}) do |boundary, memo|
|
35
|
-
if memo.dig(boundary.location, boundary.key)
|
36
|
-
raise Composer::ValidationError, "Multiple boundary queries for `#{type.graphql_name}.#{boundary.key}` "\
|
37
|
-
"found in #{boundary.location}. Limit one boundary query per type and key in each location. "\
|
38
|
-
"Abstract boundaries provide all possible types."
|
39
|
-
end
|
40
|
-
memo[boundary.location] ||= {}
|
41
|
-
memo[boundary.location][boundary.key] = boundary
|
42
|
-
end
|
43
|
-
|
44
|
-
boundary_keys = boundaries.map(&:key).to_set
|
45
|
-
|
46
|
-
# All non-key fields must be resolvable in at least one boundary location
|
47
|
-
supergraph.locations_by_type_and_field[type.graphql_name].each do |field_name, locations|
|
48
|
-
next if boundary_keys.include?(field_name)
|
49
|
-
|
50
|
-
if locations.none? { boundaries_by_location_and_key[_1] }
|
51
|
-
where = locations.length > 1 ? "one of #{locations.join(", ")} locations" : locations.first
|
52
|
-
raise Composer::ValidationError, "A boundary query is required for `#{type.graphql_name}` in #{where} to resolve field `#{field_name}`."
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# All locations of a boundary type must include at least one key field
|
57
|
-
supergraph.fields_by_type_and_location[type.graphql_name].each do |location, field_names|
|
58
|
-
if field_names.none? { boundary_keys.include?(_1) }
|
59
|
-
raise Composer::ValidationError, "A boundary key is required for `#{type.graphql_name}` in #{location} to join with other locations."
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# verify that all outbound locations can access all inbound locations
|
64
|
-
resolver_locations = boundaries_by_location_and_key.keys
|
65
|
-
candidate_types_by_location.each_key do |location|
|
66
|
-
remote_locations = resolver_locations.reject { _1 == location }
|
67
|
-
paths = supergraph.route_type_to_locations(type.graphql_name, location, remote_locations)
|
68
|
-
if paths.length != remote_locations.length || paths.any? { |_loc, path| path.nil? }
|
69
|
-
raise Composer::ValidationError, "Cannot route `#{type.graphql_name}` boundaries in #{location} to all other locations. "\
|
70
|
-
"All locations must provide a boundary accessor that uses a conjoining key."
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def validate_as_shared(supergraph, type, candidate_types_by_location)
|
76
|
-
expected_fields = begin
|
77
|
-
type.fields.keys.sort
|
78
|
-
rescue StandardError => e
|
79
|
-
# bug with inherited interfaces in older versions of GraphQL
|
80
|
-
if type.interfaces.any? { _1.is_a?(GraphQL::Schema::LateBoundType) }
|
81
|
-
raise Composer::ComposerError, "Merged interface inheritance requires GraphQL >= v2.0.3"
|
82
|
-
else
|
83
|
-
raise e
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
candidate_types_by_location.each do |location, candidate_type|
|
88
|
-
if candidate_type.fields.keys.sort != expected_fields
|
89
|
-
raise Composer::ValidationError, "Shared type `#{type.graphql_name}` must have consistent fields across locations, "\
|
90
|
-
"or else define boundary queries so that its unique fields may be accessed remotely."
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module Stitching
|
5
|
-
# Builds hidden selection fields added by stitiching code,
|
6
|
-
# used to request operational data about resolved objects.
|
7
|
-
class ExportSelection
|
8
|
-
EXPORT_PREFIX = "_export_"
|
9
|
-
|
10
|
-
class << self
|
11
|
-
@typename_node = nil
|
12
|
-
|
13
|
-
def key?(name)
|
14
|
-
return false unless name
|
15
|
-
|
16
|
-
name.start_with?(EXPORT_PREFIX)
|
17
|
-
end
|
18
|
-
|
19
|
-
def key(name)
|
20
|
-
"#{EXPORT_PREFIX}#{name}"
|
21
|
-
end
|
22
|
-
|
23
|
-
# The argument assigning Field.alias changed from
|
24
|
-
# a generic `alias` hash key to a structured `field_alias` kwarg.
|
25
|
-
# See https://github.com/rmosolgo/graphql-ruby/pull/4718
|
26
|
-
FIELD_ALIAS_KWARG = !GraphQL::Language::Nodes::Field.new(field_alias: "a").alias.nil?
|
27
|
-
|
28
|
-
def key_node(field_name)
|
29
|
-
if FIELD_ALIAS_KWARG
|
30
|
-
GraphQL::Language::Nodes::Field.new(field_alias: key(field_name), name: field_name)
|
31
|
-
else
|
32
|
-
GraphQL::Language::Nodes::Field.new(alias: key(field_name), name: field_name)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def typename_node
|
37
|
-
@typename_node ||= key_node("__typename")
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|