graphql-stitching 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/composer.md +3 -0
- data/docs/executor.md +2 -2
- data/lib/graphql/stitching/composer.rb +17 -10
- data/lib/graphql/stitching/executor/boundary_source.rb +3 -3
- data/lib/graphql/stitching/{selection_hint.rb → export_selection.rb} +4 -4
- data/lib/graphql/stitching/planner.rb +10 -8
- data/lib/graphql/stitching/request.rb +1 -1
- data/lib/graphql/stitching/shaper.rb +3 -3
- data/lib/graphql/stitching/skip_include.rb +1 -1
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d90338dfde5c26515a55afdee7f5bd936a4563b2b8f83e1ffc1a4420c56a5cf
|
4
|
+
data.tar.gz: d728c5dfabb5e0250c38a6718ac7b535cfa7a183872dc60fa95a9b91b777a64a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc5ff912f354e08b9f204028d5e6eed85a81bf283bd5dc13bb7ee9b69b83d0bb39ba83f9fbaac66be1363e86ce3f6bd1b3f7cc4466ef83998a751e3dbccf9013
|
7
|
+
data.tar.gz: 055e1c09f88e8f893a3ebb0aa3d7558156ec5c4a33b704cca69499efee4fd02e3a70e6efedeca1332f92392c7093ea8024ba9aefb159ce77b4f11510cd8d7a1c
|
data/docs/composer.md
CHANGED
@@ -12,6 +12,7 @@ composer = GraphQL::Stitching::Composer.new(
|
|
12
12
|
mutation_name: "Mutation",
|
13
13
|
description_merger: ->(values_by_location, info) { values_by_location.values.join("\n") },
|
14
14
|
deprecation_merger: ->(values_by_location, info) { values_by_location.values.first },
|
15
|
+
default_value_merger: ->(values_by_location, info) { values_by_location.values.first },
|
15
16
|
directive_kwarg_merger: ->(values_by_location, info) { values_by_location.values.last },
|
16
17
|
root_field_location_selector: ->(locations, info) { locations.last },
|
17
18
|
)
|
@@ -27,6 +28,8 @@ Constructor arguments:
|
|
27
28
|
|
28
29
|
- **`deprecation_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging element deprecation strings from across locations.
|
29
30
|
|
31
|
+
- **`default_value_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging argument default values from across locations.
|
32
|
+
|
30
33
|
- **`directive_kwarg_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging directive keyword arguments from across locations.
|
31
34
|
|
32
35
|
- **`root_field_location_selector:`** _optional_, selects a default routing location for root fields with multiple locations. Use this to prioritize sending root fields to their primary data sources (only applies while routing the root operation scope). This handler receives an array of possible locations and an info object with field information, and should return the prioritized location. The last location is used by default.
|
data/docs/executor.md
CHANGED
@@ -50,8 +50,8 @@ The raw result will contain many irregularities from the stitching process, howe
|
|
50
50
|
"data" => {
|
51
51
|
"product" => {
|
52
52
|
"upc" => "1",
|
53
|
-
"
|
54
|
-
"
|
53
|
+
"_export_upc" => "1",
|
54
|
+
"_export_typename" => "Product",
|
55
55
|
"name" => "iPhone",
|
56
56
|
"price" => nil,
|
57
57
|
}
|
@@ -5,9 +5,13 @@ module GraphQL
|
|
5
5
|
class Composer
|
6
6
|
class ComposerError < StitchingError; end
|
7
7
|
class ValidationError < ComposerError; end
|
8
|
+
class ReferenceType < GraphQL::Schema::Object
|
9
|
+
field(:f, String) do
|
10
|
+
argument(:a, String)
|
11
|
+
end
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
14
|
+
NO_DEFAULT_VALUE = ReferenceType.get_field("f").get_argument("a").default_value
|
11
15
|
BASIC_VALUE_MERGER = ->(values_by_location, _info) { values_by_location.values.find { !_1.nil? } }
|
12
16
|
BASIC_ROOT_FIELD_LOCATION_SELECTOR = ->(locations, _info) { locations.last }
|
13
17
|
|
@@ -16,6 +20,8 @@ module GraphQL
|
|
16
20
|
"ValidateBoundaries",
|
17
21
|
].freeze
|
18
22
|
|
23
|
+
attr_reader :query_name, :mutation_name, :candidate_types_by_name_and_location, :schema_directives
|
24
|
+
|
19
25
|
def initialize(
|
20
26
|
query_name: "Query",
|
21
27
|
mutation_name: "Mutation",
|
@@ -195,7 +201,7 @@ module GraphQL
|
|
195
201
|
description(builder.merge_descriptions(directive_name, directives_by_location))
|
196
202
|
repeatable(directives_by_location.values.any?(&:repeatable?))
|
197
203
|
locations(*directives_by_location.values.flat_map(&:locations).uniq)
|
198
|
-
builder.build_merged_arguments(directive_name, directives_by_location, self)
|
204
|
+
builder.build_merged_arguments(directive_name, directives_by_location, self, directive_name: directive_name)
|
199
205
|
end
|
200
206
|
end
|
201
207
|
|
@@ -343,7 +349,7 @@ module GraphQL
|
|
343
349
|
end
|
344
350
|
end
|
345
351
|
|
346
|
-
def build_merged_arguments(type_name, members_by_location, owner, field_name: nil)
|
352
|
+
def build_merged_arguments(type_name, members_by_location, owner, field_name: nil, directive_name: nil)
|
347
353
|
# "argument_name" => "location" => argument
|
348
354
|
args_by_name_location = members_by_location.each_with_object({}) do |(location, member_candidate), memo|
|
349
355
|
member_candidate.arguments.each do |argument_name, argument|
|
@@ -369,7 +375,7 @@ module GraphQL
|
|
369
375
|
|
370
376
|
kwargs = {}
|
371
377
|
default_values_by_location = arguments_by_location.each_with_object({}) do |(location, argument), memo|
|
372
|
-
next if argument.default_value
|
378
|
+
next if argument.default_value == NO_DEFAULT_VALUE
|
373
379
|
memo[location] = argument.default_value
|
374
380
|
end
|
375
381
|
|
@@ -378,7 +384,8 @@ module GraphQL
|
|
378
384
|
type_name: type_name,
|
379
385
|
field_name: field_name,
|
380
386
|
argument_name: argument_name,
|
381
|
-
|
387
|
+
directive_name: directive_name,
|
388
|
+
}.tap(&:compact!))
|
382
389
|
end
|
383
390
|
|
384
391
|
type = merge_value_types(type_name, value_types, argument_name: argument_name, field_name: field_name)
|
@@ -430,7 +437,7 @@ module GraphQL
|
|
430
437
|
enum_value: enum_value,
|
431
438
|
directive_name: directive_name,
|
432
439
|
kwarg_name: kwarg_name,
|
433
|
-
}.compact!)
|
440
|
+
}.tap(&:compact!))
|
434
441
|
end
|
435
442
|
|
436
443
|
owner.directive(directive_class, **kwargs)
|
@@ -438,7 +445,7 @@ module GraphQL
|
|
438
445
|
end
|
439
446
|
|
440
447
|
def merge_value_types(type_name, type_candidates, field_name: nil, argument_name: nil)
|
441
|
-
path = [type_name, field_name, argument_name].compact.join(".")
|
448
|
+
path = [type_name, field_name, argument_name].tap(&:compact!).join(".")
|
442
449
|
alt_structures = type_candidates.map { Util.flatten_type_structure(_1) }
|
443
450
|
basis_structure = alt_structures.shift
|
444
451
|
|
@@ -475,7 +482,7 @@ module GraphQL
|
|
475
482
|
field_name: field_name,
|
476
483
|
argument_name: argument_name,
|
477
484
|
enum_value: enum_value,
|
478
|
-
}.compact!)
|
485
|
+
}.tap(&:compact!))
|
479
486
|
end
|
480
487
|
|
481
488
|
def merge_deprecations(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
|
@@ -485,7 +492,7 @@ module GraphQL
|
|
485
492
|
field_name: field_name,
|
486
493
|
argument_name: argument_name,
|
487
494
|
enum_value: enum_value,
|
488
|
-
}.compact!)
|
495
|
+
}.tap(&:compact!))
|
489
496
|
end
|
490
497
|
|
491
498
|
def extract_boundaries(type_name, types_by_location)
|
@@ -16,7 +16,7 @@ module GraphQL
|
|
16
16
|
|
17
17
|
if op.if_type
|
18
18
|
# operations planned around unused fragment conditions should not trigger requests
|
19
|
-
origin_set.select! { _1[
|
19
|
+
origin_set.select! { _1[ExportSelection.typename_node.alias] == op.if_type }
|
20
20
|
end
|
21
21
|
|
22
22
|
memo[op] = origin_set if origin_set.any?
|
@@ -94,9 +94,9 @@ module GraphQL
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def build_key(key, origin_obj, federation: false)
|
97
|
-
key_value = JSON.generate(origin_obj[
|
97
|
+
key_value = JSON.generate(origin_obj[ExportSelection.key(key)])
|
98
98
|
if federation
|
99
|
-
"{ __typename: \"#{origin_obj[
|
99
|
+
"{ __typename: \"#{origin_obj[ExportSelection.typename_node.alias]}\", #{key}: #{key_value} }"
|
100
100
|
else
|
101
101
|
key_value
|
102
102
|
end
|
@@ -4,18 +4,18 @@ module GraphQL
|
|
4
4
|
module Stitching
|
5
5
|
# Builds hidden selection fields added by stitiching code,
|
6
6
|
# used to request operational data about resolved objects.
|
7
|
-
class
|
8
|
-
|
7
|
+
class ExportSelection
|
8
|
+
EXPORT_PREFIX = "_export_"
|
9
9
|
|
10
10
|
class << self
|
11
11
|
def key?(name)
|
12
12
|
return false unless name
|
13
13
|
|
14
|
-
name.start_with?(
|
14
|
+
name.start_with?(EXPORT_PREFIX)
|
15
15
|
end
|
16
16
|
|
17
17
|
def key(name)
|
18
|
-
"#{
|
18
|
+
"#{EXPORT_PREFIX}#{name}"
|
19
19
|
end
|
20
20
|
|
21
21
|
def key_node(field_name)
|
@@ -42,7 +42,7 @@ module GraphQL
|
|
42
42
|
# Adjoining selections not available here get split off into new entrypoints (C).
|
43
43
|
# B.3) Collect all variable definitions used within the filtered selection.
|
44
44
|
# These specify which request variables to pass along with each step.
|
45
|
-
# B.4) Add a `__typename`
|
45
|
+
# B.4) Add a `__typename` export to abstracts and types that implement fragments.
|
46
46
|
# This provides resolved type information used during execution.
|
47
47
|
#
|
48
48
|
# C) Delegate adjoining selections to new entrypoint locations.
|
@@ -199,7 +199,9 @@ module GraphQL
|
|
199
199
|
input_selections.each do |node|
|
200
200
|
case node
|
201
201
|
when GraphQL::Language::Nodes::Field
|
202
|
-
if node.
|
202
|
+
if node.alias&.start_with?(ExportSelection::EXPORT_PREFIX)
|
203
|
+
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{ExportSelection::EXPORT_PREFIX}" is a reserved prefix.)
|
204
|
+
elsif node.name == TYPENAME
|
203
205
|
locale_selections << node
|
204
206
|
next
|
205
207
|
end
|
@@ -257,10 +259,10 @@ module GraphQL
|
|
257
259
|
end
|
258
260
|
end
|
259
261
|
|
260
|
-
# B.4) Add a `__typename`
|
262
|
+
# B.4) Add a `__typename` export to abstracts and types that implement
|
261
263
|
# fragments so that resolved type information is available during execution.
|
262
264
|
if requires_typename
|
263
|
-
locale_selections <<
|
265
|
+
locale_selections << ExportSelection.typename_node
|
264
266
|
end
|
265
267
|
|
266
268
|
if remote_selections
|
@@ -274,18 +276,18 @@ module GraphQL
|
|
274
276
|
routes.each_value do |route|
|
275
277
|
route.reduce(locale_selections) do |parent_selections, boundary|
|
276
278
|
# E.1) Add the key of each boundary query into the prior location's selection set.
|
277
|
-
foreign_key =
|
279
|
+
foreign_key = ExportSelection.key(boundary.key)
|
278
280
|
has_key = false
|
279
281
|
has_typename = false
|
280
282
|
|
281
283
|
parent_selections.each do |node|
|
282
284
|
next unless node.is_a?(GraphQL::Language::Nodes::Field)
|
283
285
|
has_key ||= node.alias == foreign_key
|
284
|
-
has_typename ||= node.alias ==
|
286
|
+
has_typename ||= node.alias == ExportSelection.typename_node.alias
|
285
287
|
end
|
286
288
|
|
287
|
-
parent_selections <<
|
288
|
-
parent_selections <<
|
289
|
+
parent_selections << ExportSelection.key_node(boundary.key) unless has_key
|
290
|
+
parent_selections << ExportSelection.typename_node unless has_typename
|
289
291
|
|
290
292
|
# E.2) Add a planner step for each new entrypoint location.
|
291
293
|
add_step(
|
@@ -4,7 +4,7 @@
|
|
4
4
|
module GraphQL
|
5
5
|
module Stitching
|
6
6
|
# Shapes the final results payload to the request selection and schema definition.
|
7
|
-
# This eliminates unrequested
|
7
|
+
# This eliminates unrequested export selections and applies null bubbling.
|
8
8
|
class Shaper
|
9
9
|
def initialize(supergraph:, request:)
|
10
10
|
@supergraph = supergraph
|
@@ -21,8 +21,8 @@ module GraphQL
|
|
21
21
|
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
|
22
22
|
return nil if raw_object.nil?
|
23
23
|
|
24
|
-
typename ||= raw_object[
|
25
|
-
raw_object.reject! { |key, _v|
|
24
|
+
typename ||= raw_object[ExportSelection.typename_node.alias]
|
25
|
+
raw_object.reject! { |key, _v| ExportSelection.key?(key) }
|
26
26
|
|
27
27
|
selections.each do |node|
|
28
28
|
case node
|
data/lib/graphql/stitching.rb
CHANGED
@@ -28,12 +28,12 @@ require_relative "stitching/boundary"
|
|
28
28
|
require_relative "stitching/client"
|
29
29
|
require_relative "stitching/composer"
|
30
30
|
require_relative "stitching/executor"
|
31
|
+
require_relative "stitching/export_selection"
|
31
32
|
require_relative "stitching/http_executable"
|
32
33
|
require_relative "stitching/plan"
|
33
34
|
require_relative "stitching/planner_step"
|
34
35
|
require_relative "stitching/planner"
|
35
36
|
require_relative "stitching/request"
|
36
|
-
require_relative "stitching/selection_hint"
|
37
37
|
require_relative "stitching/shaper"
|
38
38
|
require_relative "stitching/skip_include"
|
39
39
|
require_relative "stitching/util"
|
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.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg MacWilliam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -106,12 +106,12 @@ files:
|
|
106
106
|
- lib/graphql/stitching/executor.rb
|
107
107
|
- lib/graphql/stitching/executor/boundary_source.rb
|
108
108
|
- lib/graphql/stitching/executor/root_source.rb
|
109
|
+
- lib/graphql/stitching/export_selection.rb
|
109
110
|
- lib/graphql/stitching/http_executable.rb
|
110
111
|
- lib/graphql/stitching/plan.rb
|
111
112
|
- lib/graphql/stitching/planner.rb
|
112
113
|
- lib/graphql/stitching/planner_step.rb
|
113
114
|
- lib/graphql/stitching/request.rb
|
114
|
-
- lib/graphql/stitching/selection_hint.rb
|
115
115
|
- lib/graphql/stitching/shaper.rb
|
116
116
|
- lib/graphql/stitching/skip_include.rb
|
117
117
|
- lib/graphql/stitching/supergraph.rb
|