graphql-stitching 1.2.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,10 +2,11 @@
2
2
 
3
3
  module GraphQL::Stitching
4
4
  class Executor
5
- class BoundarySource < GraphQL::Dataloader::Source
5
+ class ResolverSource < GraphQL::Dataloader::Source
6
6
  def initialize(executor, location)
7
7
  @executor = executor
8
8
  @location = location
9
+ @variables = {}
9
10
  end
10
11
 
11
12
  def fetch(ops)
@@ -28,7 +29,7 @@ module GraphQL::Stitching
28
29
  @executor.request.operation_name,
29
30
  @executor.request.operation_directives,
30
31
  )
31
- variables = @executor.request.variables.slice(*variable_names)
32
+ variables = @variables.merge!(@executor.request.variables.slice(*variable_names))
32
33
  raw_result = @executor.request.supergraph.execute_at_location(@location, query_document, variables, @executor.request)
33
34
  @executor.query_count += 1
34
35
 
@@ -41,36 +42,40 @@ module GraphQL::Stitching
41
42
  ops.map { origin_sets_by_operation[_1] ? _1.step : nil }
42
43
  end
43
44
 
44
- # Builds batched boundary queries
45
- # "query MyOperation_2_3($var:VarType) {
46
- # _0_result: list(keys:["a","b","c"]) { boundarySelections... }
47
- # _1_0_result: item(key:"x") { boundarySelections... }
48
- # _1_1_result: item(key:"y") { boundarySelections... }
49
- # _1_2_result: item(key:"z") { boundarySelections... }
45
+ # Builds batched resolver queries
46
+ # "query MyOperation_2_3($var:VarType, $_0_key:[ID!]!, $_1_0_key:ID!, $_1_1_key:ID!, $_1_2_key:ID!) {
47
+ # _0_result: list(keys: $_0_key) { resolverSelections... }
48
+ # _1_0_result: item(key: $_1_0_key) { resolverSelections... }
49
+ # _1_1_result: item(key: $_1_1_key) { resolverSelections... }
50
+ # _1_2_result: item(key: $_1_2_key) { resolverSelections... }
50
51
  # }"
51
52
  def build_document(origin_sets_by_operation, operation_name = nil, operation_directives = nil)
52
53
  variable_defs = {}
53
54
  query_fields = origin_sets_by_operation.map.with_index do |(op, origin_set), batch_index|
54
55
  variable_defs.merge!(op.variables)
55
- boundary = op.boundary
56
+ resolver = op.resolver
56
57
 
57
- if boundary.list
58
- input = origin_set.each_with_index.reduce(String.new) do |memo, (origin_obj, index)|
59
- memo << "," if index > 0
60
- memo << build_key(boundary.key, origin_obj, federation: boundary.federation)
61
- memo
58
+ if resolver.list?
59
+ variable_name = "_#{batch_index}_key"
60
+
61
+ @variables[variable_name] = origin_set.map do |origin_obj|
62
+ build_key(resolver.key, origin_obj, as_representation: resolver.representations?)
62
63
  end
63
64
 
64
- "_#{batch_index}_result: #{boundary.field}(#{boundary.arg}:[#{input}]) #{op.selections}"
65
+ variable_defs[variable_name] = "[#{resolver.arg_type_name}!]!"
66
+ "_#{batch_index}_result: #{resolver.field}(#{resolver.arg}:$#{variable_name}) #{op.selections}"
65
67
  else
66
68
  origin_set.map.with_index do |origin_obj, index|
67
- input = build_key(boundary.key, origin_obj, federation: boundary.federation)
68
- "_#{batch_index}_#{index}_result: #{boundary.field}(#{boundary.arg}:#{input}) #{op.selections}"
69
+ variable_name = "_#{batch_index}_#{index}_key"
70
+ @variables[variable_name] = build_key(resolver.key, origin_obj, as_representation: resolver.representations?)
71
+
72
+ variable_defs[variable_name] = "#{resolver.arg_type_name}!"
73
+ "_#{batch_index}_#{index}_result: #{resolver.field}(#{resolver.arg}:$#{variable_name}) #{op.selections}"
69
74
  end
70
75
  end
71
76
  end
72
77
 
73
- doc = String.new("query") # << boundary fulfillment always uses query
78
+ doc = String.new("query") # << resolver fulfillment always uses query
74
79
 
75
80
  if operation_name
76
81
  doc << " #{operation_name}"
@@ -90,15 +95,19 @@ module GraphQL::Stitching
90
95
 
91
96
  doc << "{ #{query_fields.join(" ")} }"
92
97
 
93
- return doc, variable_defs.keys
98
+ return doc, variable_defs.keys.tap do |names|
99
+ names.reject! { @variables.key?(_1) }
100
+ end
94
101
  end
95
102
 
96
- def build_key(key, origin_obj, federation: false)
97
- key_value = JSON.generate(origin_obj[ExportSelection.key(key)])
98
- if federation
99
- "{ __typename: \"#{origin_obj[ExportSelection.typename_node.alias]}\", #{key}: #{key_value} }"
103
+ def build_key(key, origin_obj, as_representation: false)
104
+ if as_representation
105
+ {
106
+ "__typename" => origin_obj[ExportSelection.typename_node.alias],
107
+ key => origin_obj[ExportSelection.key(key)],
108
+ }
100
109
  else
101
- key_value
110
+ origin_obj[ExportSelection.key(key)]
102
111
  end
103
112
  end
104
113
 
@@ -106,7 +115,7 @@ module GraphQL::Stitching
106
115
  return unless raw_result
107
116
 
108
117
  origin_sets_by_operation.each_with_index do |(op, origin_set), batch_index|
109
- results = if op.dig("boundary", "list")
118
+ results = if op.resolver.list?
110
119
  raw_result["_#{batch_index}_result"]
111
120
  else
112
121
  origin_set.map.with_index { |_, index| raw_result["_#{batch_index}_#{index}_result"] }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require_relative "./executor/boundary_source"
4
+ require_relative "./executor/resolver_source"
5
5
  require_relative "./executor/root_source"
6
6
 
7
7
  module GraphQL
@@ -55,9 +55,9 @@ module GraphQL
55
55
  tasks = @request.plan
56
56
  .ops
57
57
  .select { next_steps.include?(_1.after) }
58
- .group_by { [_1.location, _1.boundary.nil?] }
58
+ .group_by { [_1.location, _1.resolver.nil?] }
59
59
  .map do |(location, root_source), ops|
60
- source_type = root_source ? RootSource : BoundarySource
60
+ source_type = root_source ? RootSource : ResolverSource
61
61
  @dataloader.with(source_type, self, location).request_all(ops)
62
62
  end
63
63
 
@@ -14,7 +14,7 @@ module GraphQL
14
14
  :variables,
15
15
  :path,
16
16
  :if_type,
17
- :boundary,
17
+ :resolver,
18
18
  keyword_init: true
19
19
  ) do
20
20
  def as_json
@@ -27,7 +27,7 @@ module GraphQL
27
27
  variables: variables,
28
28
  path: path,
29
29
  if_type: if_type,
30
- boundary: boundary&.as_json
30
+ resolver: resolver&.as_json
31
31
  }.tap(&:compact!)
32
32
  end
33
33
  end
@@ -36,7 +36,7 @@ module GraphQL
36
36
  def from_json(json)
37
37
  ops = json["ops"]
38
38
  ops = ops.map do |op|
39
- boundary = op["boundary"]
39
+ resolver = op["resolver"]
40
40
  Op.new(
41
41
  step: op["step"],
42
42
  after: op["after"],
@@ -46,7 +46,7 @@ module GraphQL
46
46
  variables: op["variables"],
47
47
  path: op["path"],
48
48
  if_type: op["if_type"],
49
- boundary: boundary ? GraphQL::Stitching::Boundary.new(**boundary) : nil,
49
+ resolver: resolver ? GraphQL::Stitching::Resolver.new(**resolver) : nil,
50
50
  )
51
51
  end
52
52
  new(ops: ops)
@@ -18,7 +18,7 @@ module GraphQL
18
18
 
19
19
  def perform
20
20
  build_root_entrypoints
21
- expand_abstract_boundaries
21
+ expand_abstract_resolvers
22
22
  Plan.new(ops: steps.map(&:to_plan_op))
23
23
  end
24
24
 
@@ -50,16 +50,16 @@ module GraphQL
50
50
  # C.2) Distribute non-unique fields among locations that were added during C.1.
51
51
  # C.3) Distribute remaining fields among locations weighted by greatest availability.
52
52
  #
53
- # D) Create paths routing to new entrypoint locations via boundary queries.
53
+ # D) Create paths routing to new entrypoint locations via resolver queries.
54
54
  # D.1) Types joining through multiple keys route using A* search.
55
55
  # D.2) Types joining through a single key route via quick location match.
56
56
  # (D.2 is an optional optimization of D.1)
57
57
  #
58
- # E) Translate boundary pathways into new entrypoints.
59
- # E.1) Add the key of each boundary query into the prior location's selection set.
58
+ # E) Translate resolver pathways into new entrypoints.
59
+ # E.1) Add the key of each resolver query into the prior location's selection set.
60
60
  # E.2) Add a planner step for each new entrypoint location, then extract it (B).
61
61
  #
62
- # F) Wrap concrete selections targeting abstract boundaries in typed fragments.
62
+ # F) Wrap concrete selections targeting abstract resolvers in typed fragments.
63
63
  # **
64
64
 
65
65
  # adds a planning step for fetching and inserting data into the aggregate result.
@@ -71,10 +71,10 @@ module GraphQL
71
71
  variables: {},
72
72
  path: [],
73
73
  operation_type: QUERY_OP,
74
- boundary: nil
74
+ resolver: nil
75
75
  )
76
76
  # coalesce repeat parameters into a single entrypoint
77
- entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{boundary&.key}")
77
+ entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key}")
78
78
  path.each { entrypoint << "/#{_1}" }
79
79
 
80
80
  step = @steps_by_entrypoint[entrypoint]
@@ -94,7 +94,7 @@ module GraphQL
94
94
  selections: selections,
95
95
  variables: variables,
96
96
  path: path,
97
- boundary: boundary,
97
+ resolver: resolver,
98
98
  )
99
99
  else
100
100
  step.selections.concat(selections)
@@ -269,15 +269,15 @@ module GraphQL
269
269
  # C) Delegate adjoining selections to new entrypoint locations.
270
270
  remote_selections_by_location = delegate_remote_selections(parent_type, remote_selections)
271
271
 
272
- # D) Create paths routing to new entrypoint locations via boundary queries.
272
+ # D) Create paths routing to new entrypoint locations via resolver queries.
273
273
  routes = @supergraph.route_type_to_locations(parent_type.graphql_name, current_location, remote_selections_by_location.keys)
274
274
 
275
- # E) Translate boundary pathways into new entrypoints.
275
+ # E) Translate resolver pathways into new entrypoints.
276
276
  routes.each_value do |route|
277
- route.reduce(locale_selections) do |parent_selections, boundary|
278
- # E.1) Add the key of each boundary query into the prior location's selection set.
279
- if boundary.key
280
- foreign_key = ExportSelection.key(boundary.key)
277
+ route.reduce(locale_selections) do |parent_selections, resolver|
278
+ # E.1) Add the key of each resolver query into the prior location's selection set.
279
+ if resolver.key
280
+ foreign_key = ExportSelection.key(resolver.key)
281
281
  has_key = false
282
282
  has_typename = false
283
283
 
@@ -287,18 +287,18 @@ module GraphQL
287
287
  has_typename ||= node.alias == ExportSelection.typename_node.alias
288
288
  end
289
289
 
290
- parent_selections << ExportSelection.key_node(boundary.key) unless has_key
290
+ parent_selections << ExportSelection.key_node(resolver.key) unless has_key
291
291
  parent_selections << ExportSelection.typename_node unless has_typename
292
292
  end
293
293
 
294
294
  # E.2) Add a planner step for each new entrypoint location.
295
295
  add_step(
296
- location: boundary.location,
296
+ location: resolver.location,
297
297
  parent_index: parent_index,
298
298
  parent_type: parent_type,
299
- selections: remote_selections_by_location[boundary.location] || [],
299
+ selections: remote_selections_by_location[resolver.location] || [],
300
300
  path: path.dup,
301
- boundary: boundary.key ? boundary : nil,
301
+ resolver: resolver.key ? resolver : nil,
302
302
  ).selections
303
303
  end
304
304
  end
@@ -414,14 +414,14 @@ module GraphQL
414
414
  selections_by_location
415
415
  end
416
416
 
417
- # F) Wrap concrete selections targeting abstract boundaries in typed fragments.
418
- def expand_abstract_boundaries
417
+ # F) Wrap concrete selections targeting abstract resolvers in typed fragments.
418
+ def expand_abstract_resolvers
419
419
  @steps_by_entrypoint.each_value do |step|
420
- next unless step.boundary
420
+ next unless step.resolver
421
421
 
422
- boundary_type = @supergraph.memoized_schema_types[step.boundary.type_name]
423
- next unless boundary_type.kind.abstract?
424
- next if boundary_type == step.parent_type
422
+ resolver_type = @supergraph.memoized_schema_types[step.resolver.type_name]
423
+ next unless resolver_type.kind.abstract?
424
+ next if resolver_type == step.parent_type
425
425
 
426
426
  expanded_selections = nil
427
427
  step.selections.reject! do |node|
@@ -9,7 +9,7 @@ module GraphQL
9
9
  GRAPHQL_PRINTER = GraphQL::Language::Printer.new
10
10
 
11
11
  attr_reader :index, :location, :parent_type, :operation_type, :path
12
- attr_accessor :after, :selections, :variables, :boundary
12
+ attr_accessor :after, :selections, :variables, :resolver
13
13
 
14
14
  def initialize(
15
15
  location:,
@@ -20,7 +20,7 @@ module GraphQL
20
20
  selections: [],
21
21
  variables: {},
22
22
  path: [],
23
- boundary: nil
23
+ resolver: nil
24
24
  )
25
25
  @location = location
26
26
  @parent_type = parent_type
@@ -30,7 +30,7 @@ module GraphQL
30
30
  @selections = selections
31
31
  @variables = variables
32
32
  @path = path
33
- @boundary = boundary
33
+ @resolver = resolver
34
34
  end
35
35
 
36
36
  def to_plan_op
@@ -43,17 +43,17 @@ module GraphQL
43
43
  variables: rendered_variables,
44
44
  path: @path,
45
45
  if_type: type_condition,
46
- boundary: @boundary,
46
+ resolver: @resolver,
47
47
  )
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- # Concrete types going to a boundary report themselves as a type condition.
52
+ # Concrete types going to a resolver report themselves as a type condition.
53
53
  # This is used by the executor to evalute which planned fragment selections
54
54
  # actually apply to the resolved object types.
55
55
  def type_condition
56
- @parent_type.graphql_name if @boundary && !parent_type.kind.abstract?
56
+ @parent_type.graphql_name if @resolver && !parent_type.kind.abstract?
57
57
  end
58
58
 
59
59
  def rendered_selections
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Stitching
5
+ # Defines a root resolver query that provides direct access to an entity type.
6
+ Resolver = Struct.new(
7
+ # location name providing the resolver query.
8
+ :location,
9
+
10
+ # name of merged type fulfilled through this resolver.
11
+ :type_name,
12
+
13
+ # a key field to select from prior locations, sent as resolver argument.
14
+ :key,
15
+
16
+ # name of the root field to query.
17
+ :field,
18
+
19
+ # specifies when the resolver is a list query.
20
+ :list,
21
+
22
+ # name of the root field argument used to send the key.
23
+ :arg,
24
+
25
+ # type name of the root field argument used to send the key.
26
+ :arg_type_name,
27
+
28
+ # specifies that keys should be sent as JSON representations with __typename and key.
29
+ :representations,
30
+ keyword_init: true
31
+ ) do
32
+ alias_method :list?, :list
33
+ alias_method :representations?, :representations
34
+
35
+ def as_json
36
+ {
37
+ location: location,
38
+ type_name: type_name,
39
+ key: key,
40
+ field: field,
41
+ list: list,
42
+ arg: arg,
43
+ arg_type_name: arg_type_name,
44
+ representations: representations,
45
+ }.tap(&:compact!)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -10,8 +10,9 @@ module GraphQL::Stitching
10
10
  argument :key, String, required: true
11
11
  argument :field, String, required: true
12
12
  argument :arg, String, required: true
13
+ argument :arg_type_name, String, required: true
13
14
  argument :list, Boolean, required: false
14
- argument :federation, Boolean, required: false
15
+ argument :representations, Boolean, required: false
15
16
  repeatable true
16
17
  end
17
18
  end
@@ -18,7 +18,7 @@ module GraphQL
18
18
  def from_definition(schema, executables:)
19
19
  schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
20
20
  field_map = {}
21
- boundary_map = {}
21
+ resolver_map = {}
22
22
  possible_locations = {}
23
23
  introspection_types = schema.introspection_system.types.keys
24
24
 
@@ -29,15 +29,16 @@ module GraphQL
29
29
  next unless directive.graphql_name == ResolverDirective.graphql_name
30
30
 
31
31
  kwargs = directive.arguments.keyword_arguments
32
- boundary_map[type_name] ||= []
33
- boundary_map[type_name] << Boundary.new(
32
+ resolver_map[type_name] ||= []
33
+ resolver_map[type_name] << Resolver.new(
34
34
  type_name: kwargs.fetch(:type_name, type_name),
35
35
  location: kwargs[:location],
36
36
  key: kwargs[:key],
37
37
  field: kwargs[:field],
38
- arg: kwargs[:arg],
39
38
  list: kwargs[:list] || false,
40
- federation: kwargs[:federation] || false,
39
+ arg: kwargs[:arg],
40
+ arg_type_name: kwargs[:arg_type_name],
41
+ representations: kwargs[:representations] || false,
41
42
  )
42
43
  end
43
44
 
@@ -66,7 +67,7 @@ module GraphQL
66
67
  new(
67
68
  schema: schema,
68
69
  fields: field_map,
69
- boundaries: boundary_map,
70
+ resolvers: resolver_map,
70
71
  executables: executables,
71
72
  )
72
73
  end
@@ -78,13 +79,13 @@ module GraphQL
78
79
  # @return [Hash<String, Executable>] a map of executable resources by location.
79
80
  attr_reader :executables
80
81
 
81
- attr_reader :boundaries, :locations_by_type_and_field
82
+ attr_reader :resolvers, :locations_by_type_and_field
82
83
 
83
- def initialize(schema:, fields: {}, boundaries: {}, executables: {})
84
+ def initialize(schema:, fields: {}, resolvers: {}, executables: {})
84
85
  @schema = schema
85
86
  @schema.use(GraphQL::Schema::AlwaysVisible)
86
87
 
87
- @boundaries = boundaries
88
+ @resolvers = resolvers
88
89
  @fields_by_type_and_location = nil
89
90
  @locations_by_type = nil
90
91
  @memoized_introspection_types = nil
@@ -120,27 +121,28 @@ module GraphQL
120
121
  end
121
122
 
122
123
  @schema.types.each do |type_name, type|
123
- if boundaries_for_type = @boundaries.dig(type_name)
124
- boundaries_for_type.each do |boundary|
124
+ if resolvers_for_type = @resolvers.dig(type_name)
125
+ resolvers_for_type.each do |resolver|
125
126
  existing = type.directives.find do |d|
126
127
  kwargs = d.arguments.keyword_arguments
127
128
  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
129
+ kwargs[:location] == resolver.location &&
130
+ kwargs[:key] == resolver.key &&
131
+ kwargs[:field] == resolver.field &&
132
+ kwargs[:arg] == resolver.arg &&
133
+ kwargs.fetch(:list, false) == resolver.list &&
134
+ kwargs.fetch(:representations, false) == resolver.representations
134
135
  end
135
136
 
136
137
  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,
138
+ type_name: (resolver.type_name if resolver.type_name != type_name),
139
+ location: resolver.location,
140
+ key: resolver.key,
141
+ field: resolver.field,
142
+ list: resolver.list || nil,
143
+ arg: resolver.arg,
144
+ arg_type_name: resolver.arg_type_name,
145
+ representations: resolver.representations || nil,
144
146
  }.tap(&:compact!)) if existing.nil?
145
147
  end
146
148
  end
@@ -242,19 +244,19 @@ module GraphQL
242
244
  end
243
245
  end
244
246
 
245
- # collects all possible boundary keys for a given type
247
+ # collects all possible resolver keys for a given type
246
248
  # ("Type") => ["id", ...]
247
249
  def possible_keys_for_type(type_name)
248
250
  @possible_keys_by_type[type_name] ||= begin
249
251
  if type_name == @schema.query.graphql_name
250
252
  GraphQL::Stitching::EMPTY_ARRAY
251
253
  else
252
- @boundaries[type_name].map(&:key).tap(&:uniq!)
254
+ @resolvers[type_name].map(&:key).tap(&:uniq!)
253
255
  end
254
256
  end
255
257
  end
256
258
 
257
- # collects possible boundary keys for a given type and location
259
+ # collects possible resolver keys for a given type and location
258
260
  # ("Type", "location") => ["id", ...]
259
261
  def possible_keys_for_type_and_location(type_name, location)
260
262
  possible_keys_by_type = @possible_keys_by_type_and_location[type_name] ||= {}
@@ -265,14 +267,14 @@ module GraphQL
265
267
  end
266
268
 
267
269
  # 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
270
+ # used to connect a partial type across locations via resolver queries
269
271
  def route_type_to_locations(type_name, start_location, goal_locations)
270
272
  key_count = possible_keys_for_type(type_name).length
271
273
 
272
274
  if key_count.zero?
273
- # nested root scopes have no boundary keys and just return a location
275
+ # nested root scopes have no resolver keys and just return a location
274
276
  goal_locations.each_with_object({}) do |goal_location, memo|
275
- memo[goal_location] = [Boundary.new(location: goal_location)]
277
+ memo[goal_location] = [Resolver.new(location: goal_location)]
276
278
  end
277
279
 
278
280
  elsif key_count > 1
@@ -281,10 +283,10 @@ module GraphQL
281
283
 
282
284
  else
283
285
  # 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]
286
+ # so can use a simple match to collect resolvers for the goal locations.
287
+ @resolvers[type_name].each_with_object({}) do |resolver, memo|
288
+ if goal_locations.include?(resolver.location)
289
+ memo[resolver.location] = [resolver]
288
290
  end
289
291
  end
290
292
  end
@@ -292,7 +294,7 @@ module GraphQL
292
294
 
293
295
  private
294
296
 
295
- PathNode = Struct.new(:location, :key, :cost, :boundary, keyword_init: true)
297
+ PathNode = Struct.new(:location, :key, :cost, :resolver, keyword_init: true)
296
298
 
297
299
  # tunes A* search to favor paths with fewest joining locations, ie:
298
300
  # favor longer paths through target locations over shorter paths with additional locations.
@@ -310,9 +312,9 @@ module GraphQL
310
312
  current_key = path.last.key
311
313
  current_cost = path.last.cost
312
314
 
313
- @boundaries[type_name].each do |boundary|
314
- forward_location = boundary.location
315
- next if current_key != boundary.key
315
+ @resolvers[type_name].each do |resolver|
316
+ forward_location = resolver.location
317
+ next if current_key != resolver.key
316
318
  next if path.any? { _1.location == forward_location }
317
319
 
318
320
  best_cost = costs[forward_location] || Float::INFINITY
@@ -323,13 +325,13 @@ module GraphQL
323
325
  location: current_location,
324
326
  key: current_key,
325
327
  cost: current_cost,
326
- boundary: boundary,
328
+ resolver: resolver,
327
329
  )
328
330
 
329
331
  if goal_locations.include?(forward_location)
330
332
  current_result = results[forward_location]
331
333
  if current_result.nil? || current_cost < best_cost || (current_cost == best_cost && path.length < current_result.length)
332
- results[forward_location] = path.map(&:boundary)
334
+ results[forward_location] = path.map(&:resolver)
333
335
  end
334
336
  else
335
337
  path.last.cost += 1
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Stitching
5
- VERSION = "1.2.4"
5
+ VERSION = "1.3.0"
6
6
  end
7
7
  end
@@ -24,7 +24,7 @@ module GraphQL
24
24
  end
25
25
 
26
26
  require_relative "stitching/supergraph"
27
- require_relative "stitching/boundary"
27
+ require_relative "stitching/resolver"
28
28
  require_relative "stitching/client"
29
29
  require_relative "stitching/composer"
30
30
  require_relative "stitching/executor"
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.2.4
4
+ version: 1.3.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-04-07 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -110,15 +110,14 @@ files:
110
110
  - gemfiles/graphql_2.2.0.gemfile
111
111
  - graphql-stitching.gemspec
112
112
  - lib/graphql/stitching.rb
113
- - lib/graphql/stitching/boundary.rb
114
113
  - lib/graphql/stitching/client.rb
115
114
  - lib/graphql/stitching/composer.rb
116
115
  - lib/graphql/stitching/composer/base_validator.rb
117
- - lib/graphql/stitching/composer/boundary_config.rb
118
- - lib/graphql/stitching/composer/validate_boundaries.rb
116
+ - lib/graphql/stitching/composer/resolver_config.rb
119
117
  - lib/graphql/stitching/composer/validate_interfaces.rb
118
+ - lib/graphql/stitching/composer/validate_resolvers.rb
120
119
  - lib/graphql/stitching/executor.rb
121
- - lib/graphql/stitching/executor/boundary_source.rb
120
+ - lib/graphql/stitching/executor/resolver_source.rb
122
121
  - lib/graphql/stitching/executor/root_source.rb
123
122
  - lib/graphql/stitching/export_selection.rb
124
123
  - lib/graphql/stitching/http_executable.rb
@@ -126,6 +125,7 @@ files:
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
129
  - lib/graphql/stitching/shaper.rb
130
130
  - lib/graphql/stitching/skip_include.rb
131
131
  - lib/graphql/stitching/supergraph.rb