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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -17
  3. data/docs/README.md +2 -1
  4. data/docs/mechanics.md +2 -1
  5. data/docs/resolver.md +101 -0
  6. data/lib/graphql/stitching/client.rb +5 -1
  7. data/lib/graphql/stitching/composer/{boundary_config.rb → resolver_config.rb} +18 -13
  8. data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
  9. data/lib/graphql/stitching/composer/validate_resolvers.rb +97 -0
  10. data/lib/graphql/stitching/composer.rb +107 -112
  11. data/lib/graphql/stitching/executor/{boundary_source.rb → resolver_source.rb} +40 -32
  12. data/lib/graphql/stitching/executor.rb +3 -3
  13. data/lib/graphql/stitching/plan.rb +3 -4
  14. data/lib/graphql/stitching/planner.rb +30 -41
  15. data/lib/graphql/stitching/planner_step.rb +6 -6
  16. data/lib/graphql/stitching/resolver/arguments.rb +284 -0
  17. data/lib/graphql/stitching/resolver/keys.rb +206 -0
  18. data/lib/graphql/stitching/resolver.rb +70 -0
  19. data/lib/graphql/stitching/shaper.rb +3 -3
  20. data/lib/graphql/stitching/skip_include.rb +1 -1
  21. data/lib/graphql/stitching/supergraph/key_directive.rb +13 -0
  22. data/lib/graphql/stitching/supergraph/resolver_directive.rb +4 -4
  23. data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
  24. data/lib/graphql/stitching/supergraph.rb +31 -144
  25. data/lib/graphql/stitching/util.rb +28 -0
  26. data/lib/graphql/stitching/version.rb +1 -1
  27. data/lib/graphql/stitching.rb +3 -2
  28. metadata +11 -7
  29. data/lib/graphql/stitching/boundary.rb +0 -29
  30. data/lib/graphql/stitching/composer/validate_boundaries.rb +0 -96
  31. data/lib/graphql/stitching/export_selection.rb +0 -42
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./resolver/arguments"
4
+ require_relative "./resolver/keys"
5
+
6
+ module GraphQL
7
+ module Stitching
8
+ # Defines a root resolver query that provides direct access to an entity type.
9
+ class Resolver
10
+ extend ArgumentsParser
11
+ extend KeysParser
12
+
13
+ # location name providing the resolver query.
14
+ attr_reader :location
15
+
16
+ # name of merged type fulfilled through this resolver.
17
+ attr_reader :type_name
18
+
19
+ # name of the root field to query.
20
+ attr_reader :field
21
+
22
+ # a key field to select from prior locations, sent as resolver argument.
23
+ attr_reader :key
24
+
25
+ # parsed resolver Argument structures.
26
+ attr_reader :arguments
27
+
28
+ def initialize(
29
+ location:,
30
+ type_name: nil,
31
+ list: false,
32
+ field: nil,
33
+ key: nil,
34
+ arguments: nil
35
+ )
36
+ @location = location
37
+ @type_name = type_name
38
+ @list = list
39
+ @field = field
40
+ @key = key
41
+ @arguments = arguments
42
+ end
43
+
44
+ # specifies when the resolver is a list query.
45
+ def list?
46
+ @list
47
+ end
48
+
49
+ def version
50
+ @version ||= Digest::SHA2.hexdigest(as_json.to_json)
51
+ end
52
+
53
+ def ==(other)
54
+ self.class == other.class && self.as_json == other.as_json
55
+ end
56
+
57
+ def as_json
58
+ {
59
+ location: location,
60
+ type_name: type_name,
61
+ list: list?,
62
+ field: field,
63
+ key: key.to_definition,
64
+ arguments: arguments.map(&:to_definition).join(", "),
65
+ argument_types: arguments.map(&:to_type_definition).join(", "),
66
+ }.tap(&:compact!)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -23,8 +23,8 @@ module GraphQL
23
23
  def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
24
24
  return nil if raw_object.nil?
25
25
 
26
- typename ||= raw_object[ExportSelection.typename_node.alias]
27
- raw_object.reject! { |key, _v| ExportSelection.key?(key) }
26
+ typename ||= raw_object[Resolver::TYPENAME_EXPORT_NODE.alias]
27
+ raw_object.reject! { |key, _v| Resolver.export_key?(key) }
28
28
 
29
29
  selections.each do |node|
30
30
  case node
@@ -64,7 +64,7 @@ module GraphQL
64
64
  return nil if result.nil?
65
65
 
66
66
  else
67
- raise "Unexpected node of type #{node.class.name} in selection set."
67
+ raise StitchingError, "Unexpected node of type #{node.class.name} in selection set."
68
68
  end
69
69
  end
70
70
 
@@ -40,7 +40,7 @@ module GraphQL
40
40
  end
41
41
 
42
42
  if filtered_selections.none?
43
- filtered_selections << ExportSelection.typename_node
43
+ filtered_selections << Resolver::TYPENAME_EXPORT_NODE
44
44
  end
45
45
 
46
46
  if changed
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL::Stitching
4
+ class Supergraph
5
+ class KeyDirective < GraphQL::Schema::Directive
6
+ graphql_name "key"
7
+ locations OBJECT, INTERFACE, UNION
8
+ argument :key, String, required: true
9
+ argument :location, String, required: true
10
+ repeatable true
11
+ end
12
+ end
13
+ end
@@ -5,13 +5,13 @@ module GraphQL::Stitching
5
5
  class ResolverDirective < GraphQL::Schema::Directive
6
6
  graphql_name "resolver"
7
7
  locations OBJECT, INTERFACE, UNION
8
- argument :type_name, String, required: false
9
8
  argument :location, String, required: true
9
+ argument :list, Boolean, required: false
10
10
  argument :key, String, required: true
11
11
  argument :field, String, required: true
12
- argument :arg, String, required: true
13
- argument :list, Boolean, required: false
14
- argument :federation, Boolean, required: false
12
+ argument :arguments, String, required: true
13
+ argument :argument_types, String, required: true
14
+ argument :type_name, String, required: false
15
15
  repeatable true
16
16
  end
17
17
  end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./key_directive"
3
+ require_relative "./resolver_directive"
4
+ require_relative "./source_directive"
5
+
6
+ module GraphQL::Stitching
7
+ class Supergraph
8
+ class << self
9
+ def validate_executable!(location, executable)
10
+ return true if executable.is_a?(Class) && executable <= GraphQL::Schema
11
+ return true if executable && executable.respond_to?(:call)
12
+ raise StitchingError, "Invalid executable provided for location `#{location}`."
13
+ end
14
+
15
+ def from_definition(schema, executables:)
16
+ schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
17
+ field_map = {}
18
+ resolver_map = {}
19
+ possible_locations = {}
20
+ introspection_types = schema.introspection_system.types.keys
21
+
22
+ schema.types.each do |type_name, type|
23
+ next if introspection_types.include?(type_name)
24
+
25
+ # Collect/build key definitions for each type
26
+ locations_by_key = type.directives.each_with_object({}) do |directive, memo|
27
+ next unless directive.graphql_name == KeyDirective.graphql_name
28
+
29
+ kwargs = directive.arguments.keyword_arguments
30
+ memo[kwargs[:key]] ||= []
31
+ memo[kwargs[:key]] << kwargs[:location]
32
+ end
33
+
34
+ key_definitions = locations_by_key.each_with_object({}) do |(key, locations), memo|
35
+ memo[key] = Resolver.parse_key(key, locations)
36
+ end
37
+
38
+ # Collect/build resolver definitions for each type
39
+ type.directives.each do |directive|
40
+ next unless directive.graphql_name == ResolverDirective.graphql_name
41
+
42
+ kwargs = directive.arguments.keyword_arguments
43
+ resolver_map[type_name] ||= []
44
+ resolver_map[type_name] << Resolver.new(
45
+ location: kwargs[:location],
46
+ type_name: kwargs.fetch(:type_name, type_name),
47
+ field: kwargs[:field],
48
+ list: kwargs[:list] || false,
49
+ key: key_definitions[kwargs[:key]],
50
+ arguments: Resolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
51
+ )
52
+ end
53
+
54
+ next unless type.kind.fields?
55
+
56
+ type.fields.each do |field_name, field|
57
+ # Collection locations for each field definition
58
+ field.directives.each do |d|
59
+ next unless d.graphql_name == SourceDirective.graphql_name
60
+
61
+ location = d.arguments.keyword_arguments[:location]
62
+ field_map[type_name] ||= {}
63
+ field_map[type_name][field_name] ||= []
64
+ field_map[type_name][field_name] << location
65
+ possible_locations[location] = true
66
+ end
67
+ end
68
+ end
69
+
70
+ executables = possible_locations.keys.each_with_object({}) do |location, memo|
71
+ executable = executables[location] || executables[location.to_sym]
72
+ if validate_executable!(location, executable)
73
+ memo[location] = executable
74
+ end
75
+ end
76
+
77
+ new(
78
+ schema: schema,
79
+ fields: field_map,
80
+ resolvers: resolver_map,
81
+ executables: executables,
82
+ )
83
+ end
84
+ end
85
+
86
+ def to_definition
87
+ if @schema.directives[KeyDirective.graphql_name].nil?
88
+ @schema.directive(KeyDirective)
89
+ end
90
+ if @schema.directives[ResolverDirective.graphql_name].nil?
91
+ @schema.directive(ResolverDirective)
92
+ end
93
+ if @schema.directives[SourceDirective.graphql_name].nil?
94
+ @schema.directive(SourceDirective)
95
+ end
96
+
97
+ @schema.types.each do |type_name, type|
98
+ if resolvers_for_type = @resolvers.dig(type_name)
99
+ # Apply key directives for each unique type/key/location
100
+ # (this allows keys to be composite selections and/or omitted from the supergraph schema)
101
+ keys_for_type = resolvers_for_type.each_with_object({}) do |resolver, memo|
102
+ memo[resolver.key.to_definition] ||= Set.new
103
+ memo[resolver.key.to_definition].merge(resolver.key.locations)
104
+ end
105
+
106
+ keys_for_type.each do |key, locations|
107
+ locations.each do |location|
108
+ params = { key: key, location: location }
109
+
110
+ unless has_directive?(type, KeyDirective.graphql_name, params)
111
+ type.directive(KeyDirective, **params)
112
+ end
113
+ end
114
+ end
115
+
116
+ # Apply resolver directives for each unique query resolver
117
+ resolvers_for_type.each do |resolver|
118
+ params = {
119
+ location: resolver.location,
120
+ field: resolver.field,
121
+ list: resolver.list? || nil,
122
+ key: resolver.key.to_definition,
123
+ arguments: resolver.arguments.map(&:to_definition).join(", "),
124
+ argument_types: resolver.arguments.map(&:to_type_definition).join(", "),
125
+ type_name: (resolver.type_name if resolver.type_name != type_name),
126
+ }
127
+
128
+ unless has_directive?(type, ResolverDirective.graphql_name, params)
129
+ type.directive(ResolverDirective, **params.tap(&:compact!))
130
+ end
131
+ end
132
+ end
133
+
134
+ next unless type.kind.fields?
135
+
136
+ type.fields.each do |field_name, field|
137
+ locations_for_field = @locations_by_type_and_field.dig(type_name, field_name)
138
+ next if locations_for_field.nil?
139
+
140
+ # Apply source directives to annotate the possible locations of each field
141
+ locations_for_field.each do |location|
142
+ params = { location: location }
143
+
144
+ unless has_directive?(field, SourceDirective.graphql_name, params)
145
+ field.directive(SourceDirective, **params)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ @schema.to_definition
152
+ end
153
+
154
+ private
155
+
156
+ def has_directive?(element, directive_name, params)
157
+ existing = element.directives.find do |d|
158
+ kwargs = d.arguments.keyword_arguments
159
+ d.graphql_name == directive_name && params.all? { |k, v| kwargs[k] == v }
160
+ end
161
+
162
+ !existing.nil?
163
+ end
164
+ end
165
+ end
@@ -1,90 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./supergraph/resolver_directive"
4
- require_relative "./supergraph/source_directive"
3
+ require_relative "./supergraph/to_definition"
5
4
 
6
5
  module GraphQL
7
6
  module Stitching
8
7
  class Supergraph
9
8
  SUPERGRAPH_LOCATION = "__super"
10
9
 
11
- class << self
12
- def validate_executable!(location, executable)
13
- return true if executable.is_a?(Class) && executable <= GraphQL::Schema
14
- return true if executable && executable.respond_to?(:call)
15
- raise StitchingError, "Invalid executable provided for location `#{location}`."
16
- end
17
-
18
- def from_definition(schema, executables:)
19
- schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
20
- field_map = {}
21
- boundary_map = {}
22
- possible_locations = {}
23
- introspection_types = schema.introspection_system.types.keys
24
-
25
- schema.types.each do |type_name, type|
26
- next if introspection_types.include?(type_name)
27
-
28
- type.directives.each do |directive|
29
- next unless directive.graphql_name == ResolverDirective.graphql_name
30
-
31
- kwargs = directive.arguments.keyword_arguments
32
- boundary_map[type_name] ||= []
33
- boundary_map[type_name] << Boundary.new(
34
- type_name: kwargs.fetch(:type_name, type_name),
35
- location: kwargs[:location],
36
- key: kwargs[:key],
37
- field: kwargs[:field],
38
- arg: kwargs[:arg],
39
- list: kwargs[:list] || false,
40
- federation: kwargs[:federation] || false,
41
- )
42
- end
43
-
44
- next unless type.kind.fields?
45
-
46
- type.fields.each do |field_name, field|
47
- field.directives.each do |d|
48
- next unless d.graphql_name == SourceDirective.graphql_name
49
-
50
- location = d.arguments.keyword_arguments[:location]
51
- field_map[type_name] ||= {}
52
- field_map[type_name][field_name] ||= []
53
- field_map[type_name][field_name] << location
54
- possible_locations[location] = true
55
- end
56
- end
57
- end
58
-
59
- executables = possible_locations.keys.each_with_object({}) do |location, memo|
60
- executable = executables[location] || executables[location.to_sym]
61
- if validate_executable!(location, executable)
62
- memo[location] = executable
63
- end
64
- end
65
-
66
- new(
67
- schema: schema,
68
- fields: field_map,
69
- boundaries: boundary_map,
70
- executables: executables,
71
- )
72
- end
73
- end
74
-
75
10
  # @return [GraphQL::Schema] the composed schema for the supergraph.
76
11
  attr_reader :schema
77
12
 
78
13
  # @return [Hash<String, Executable>] a map of executable resources by location.
79
14
  attr_reader :executables
80
15
 
81
- attr_reader :boundaries, :locations_by_type_and_field
16
+ attr_reader :resolvers, :locations_by_type_and_field
82
17
 
83
- def initialize(schema:, fields: {}, boundaries: {}, executables: {})
18
+ def initialize(schema:, fields: {}, resolvers: {}, executables: {})
84
19
  @schema = schema
85
20
  @schema.use(GraphQL::Schema::AlwaysVisible)
86
21
 
87
- @boundaries = boundaries
22
+ @resolvers = resolvers
23
+ @resolvers_by_version = nil
88
24
  @fields_by_type_and_location = nil
89
25
  @locations_by_type = nil
90
26
  @memoized_introspection_types = nil
@@ -111,65 +47,17 @@ module GraphQL
111
47
  end.freeze
112
48
  end
113
49
 
114
- def to_definition
115
- if @schema.directives[ResolverDirective.graphql_name].nil?
116
- @schema.directive(ResolverDirective)
117
- end
118
- if @schema.directives[SourceDirective.graphql_name].nil?
119
- @schema.directive(SourceDirective)
120
- end
121
-
122
- @schema.types.each do |type_name, type|
123
- if boundaries_for_type = @boundaries.dig(type_name)
124
- boundaries_for_type.each do |boundary|
125
- existing = type.directives.find do |d|
126
- kwargs = d.arguments.keyword_arguments
127
- d.graphql_name == ResolverDirective.graphql_name &&
128
- kwargs[:location] == boundary.location &&
129
- kwargs[:key] == boundary.key &&
130
- kwargs[:field] == boundary.field &&
131
- kwargs[:arg] == boundary.arg &&
132
- kwargs.fetch(:list, false) == boundary.list &&
133
- kwargs.fetch(:federation, false) == boundary.federation
134
- end
135
-
136
- type.directive(ResolverDirective, **{
137
- type_name: (boundary.type_name if boundary.type_name != type_name),
138
- location: boundary.location,
139
- key: boundary.key,
140
- field: boundary.field,
141
- arg: boundary.arg,
142
- list: boundary.list || nil,
143
- federation: boundary.federation || nil,
144
- }.tap(&:compact!)) if existing.nil?
145
- end
146
- end
147
-
148
- next unless type.kind.fields?
149
-
150
- type.fields.each do |field_name, field|
151
- locations_for_field = @locations_by_type_and_field.dig(type_name, field_name)
152
- next if locations_for_field.nil?
153
-
154
- locations_for_field.each do |location|
155
- existing = field.directives.find do |d|
156
- d.graphql_name == SourceDirective.graphql_name &&
157
- d.arguments.keyword_arguments[:location] == location
158
- end
159
-
160
- field.directive(SourceDirective, location: location) if existing.nil?
161
- end
162
- end
163
- end
164
-
165
- @schema.to_definition
166
- end
167
-
168
50
  # @return [GraphQL::StaticValidation::Validator] static validator for the supergraph schema.
169
51
  def static_validator
170
52
  @static_validator ||= @schema.static_validator
171
53
  end
172
54
 
55
+ def resolvers_by_version
56
+ @resolvers_by_version ||= resolvers.values.tap(&:flatten!).each_with_object({}) do |resolver, memo|
57
+ memo[resolver.version] = resolver
58
+ end
59
+ end
60
+
173
61
  def fields
174
62
  @locations_by_type_and_field.reject { |k, _v| memoized_introspection_types[k] }
175
63
  end
@@ -242,37 +130,36 @@ module GraphQL
242
130
  end
243
131
  end
244
132
 
245
- # collects all possible boundary keys for a given type
246
- # ("Type") => ["id", ...]
133
+ # collects all possible resolver keys for a given type
134
+ # ("Type") => [Key("id"), ...]
247
135
  def possible_keys_for_type(type_name)
248
136
  @possible_keys_by_type[type_name] ||= begin
249
137
  if type_name == @schema.query.graphql_name
250
138
  GraphQL::Stitching::EMPTY_ARRAY
251
139
  else
252
- @boundaries[type_name].map(&:key).tap(&:uniq!)
140
+ @resolvers[type_name].map(&:key).uniq(&:to_definition)
253
141
  end
254
142
  end
255
143
  end
256
144
 
257
- # collects possible boundary keys for a given type and location
258
- # ("Type", "location") => ["id", ...]
145
+ # collects possible resolver keys for a given type and location
146
+ # ("Type", "location") => [Key("id"), ...]
259
147
  def possible_keys_for_type_and_location(type_name, location)
260
148
  possible_keys_by_type = @possible_keys_by_type_and_location[type_name] ||= {}
261
- possible_keys_by_type[location] ||= begin
262
- location_fields = fields_by_type_and_location[type_name][location] || []
263
- location_fields & possible_keys_for_type(type_name)
149
+ possible_keys_by_type[location] ||= possible_keys_for_type(type_name).select do |key|
150
+ key.locations.include?(location)
264
151
  end
265
152
  end
266
153
 
267
154
  # For a given type, route from one origin location to one or more remote locations
268
- # used to connect a partial type across locations via boundary queries
155
+ # used to connect a partial type across locations via resolver queries
269
156
  def route_type_to_locations(type_name, start_location, goal_locations)
270
157
  key_count = possible_keys_for_type(type_name).length
271
158
 
272
159
  if key_count.zero?
273
- # nested root scopes have no boundary keys and just return a location
160
+ # nested root scopes have no resolver keys and just return a location
274
161
  goal_locations.each_with_object({}) do |goal_location, memo|
275
- memo[goal_location] = [Boundary.new(location: goal_location)]
162
+ memo[goal_location] = [Resolver.new(location: goal_location)]
276
163
  end
277
164
 
278
165
  elsif key_count > 1
@@ -281,10 +168,10 @@ module GraphQL
281
168
 
282
169
  else
283
170
  # types with a single key attribute must all be within a single hop of each other,
284
- # so can use a simple match to collect boundaries for the goal locations.
285
- @boundaries[type_name].each_with_object({}) do |boundary, memo|
286
- if goal_locations.include?(boundary.location)
287
- memo[boundary.location] = [boundary]
171
+ # so can use a simple match to collect resolvers for the goal locations.
172
+ @resolvers[type_name].each_with_object({}) do |resolver, memo|
173
+ if goal_locations.include?(resolver.location)
174
+ memo[resolver.location] = [resolver]
288
175
  end
289
176
  end
290
177
  end
@@ -292,7 +179,7 @@ module GraphQL
292
179
 
293
180
  private
294
181
 
295
- PathNode = Struct.new(:location, :key, :cost, :boundary, keyword_init: true)
182
+ PathNode = Struct.new(:location, :key, :cost, :resolver, keyword_init: true)
296
183
 
297
184
  # tunes A* search to favor paths with fewest joining locations, ie:
298
185
  # favor longer paths through target locations over shorter paths with additional locations.
@@ -310,9 +197,9 @@ module GraphQL
310
197
  current_key = path.last.key
311
198
  current_cost = path.last.cost
312
199
 
313
- @boundaries[type_name].each do |boundary|
314
- forward_location = boundary.location
315
- next if current_key != boundary.key
200
+ @resolvers[type_name].each do |resolver|
201
+ forward_location = resolver.location
202
+ next if current_key != resolver.key
316
203
  next if path.any? { _1.location == forward_location }
317
204
 
318
205
  best_cost = costs[forward_location] || Float::INFINITY
@@ -323,13 +210,13 @@ module GraphQL
323
210
  location: current_location,
324
211
  key: current_key,
325
212
  cost: current_cost,
326
- boundary: boundary,
213
+ resolver: resolver,
327
214
  )
328
215
 
329
216
  if goal_locations.include?(forward_location)
330
217
  current_result = results[forward_location]
331
218
  if current_result.nil? || current_cost < best_cost || (current_cost == best_cost && path.length < current_result.length)
332
- results[forward_location] = path.map(&:boundary)
219
+ results[forward_location] = path.map(&:resolver)
333
220
  end
334
221
  else
335
222
  path.last.cost += 1
@@ -47,6 +47,34 @@ module GraphQL
47
47
  structure
48
48
  end
49
49
 
50
+ # builds a single-dimensional representation of a wrapped type structure from AST
51
+ def flatten_ast_type_structure(ast, structure: [])
52
+ null = true
53
+
54
+ while ast.is_a?(GraphQL::Language::Nodes::NonNullType)
55
+ ast = ast.of_type
56
+ null = false
57
+ end
58
+
59
+ if ast.is_a?(GraphQL::Language::Nodes::ListType)
60
+ structure << TypeStructure.new(
61
+ list: true,
62
+ null: null,
63
+ name: nil,
64
+ )
65
+
66
+ flatten_ast_type_structure(ast.of_type, structure: structure)
67
+ else
68
+ structure << TypeStructure.new(
69
+ list: false,
70
+ null: null,
71
+ name: ast.name,
72
+ )
73
+ end
74
+
75
+ structure
76
+ end
77
+
50
78
  # expands interfaces and unions to an array of their memberships
51
79
  # like `schema.possible_types`, but includes child interfaces
52
80
  def expand_abstract_type(schema, parent_type)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Stitching
5
- VERSION = "1.2.5"
5
+ VERSION = "1.4.0"
6
6
  end
7
7
  end
@@ -8,6 +8,8 @@ module GraphQL
8
8
  EMPTY_ARRAY = [].freeze
9
9
 
10
10
  class StitchingError < StandardError; end
11
+ class CompositionError < StitchingError; end
12
+ class ValidationError < CompositionError; end
11
13
 
12
14
  class << self
13
15
  def stitch_directive
@@ -24,11 +26,10 @@ module GraphQL
24
26
  end
25
27
 
26
28
  require_relative "stitching/supergraph"
27
- require_relative "stitching/boundary"
29
+ require_relative "stitching/resolver"
28
30
  require_relative "stitching/client"
29
31
  require_relative "stitching/composer"
30
32
  require_relative "stitching/executor"
31
- require_relative "stitching/export_selection"
32
33
  require_relative "stitching/http_executable"
33
34
  require_relative "stitching/plan"
34
35
  require_relative "stitching/planner_step"