graphql-stitching 1.6.2 → 1.7.1
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 +57 -429
- data/docs/composing_a_supergraph.md +215 -0
- data/docs/error_handling.md +69 -0
- data/docs/executables.md +112 -0
- data/docs/introduction.md +17 -0
- data/docs/merged_types.md +457 -0
- data/docs/{federation_entities.md → merged_types_apollo.md} +1 -1
- data/docs/performance.md +71 -0
- data/docs/query_planning.md +102 -0
- data/docs/serving_a_supergraph.md +152 -0
- data/docs/subscriptions.md +1 -1
- data/docs/visibility.md +178 -0
- data/lib/graphql/stitching/client.rb +11 -3
- data/lib/graphql/stitching/composer.rb +55 -26
- data/lib/graphql/stitching/{composer/supergraph_directives.rb → directives.rb} +22 -4
- data/lib/graphql/stitching/request.rb +4 -0
- data/lib/graphql/stitching/supergraph/from_definition.rb +20 -7
- data/lib/graphql/stitching/supergraph/types.rb +74 -0
- data/lib/graphql/stitching/supergraph.rb +12 -4
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +21 -2
- metadata +14 -12
- data/docs/README.md +0 -19
- data/docs/client.md +0 -107
- data/docs/composer.md +0 -125
- data/docs/http_executable.md +0 -51
- data/docs/mechanics.md +0 -306
- data/docs/request.md +0 -34
- data/docs/supergraph.md +0 -31
- data/docs/type_resolver.md +0 -101
@@ -10,17 +10,29 @@ module GraphQL::Stitching
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def from_definition(schema, executables:)
|
13
|
-
|
13
|
+
if schema.is_a?(String)
|
14
|
+
schema = if GraphQL::Stitching.supports_visibility?
|
15
|
+
GraphQL::Schema.from_definition(schema, base_types: BASE_TYPES)
|
16
|
+
else
|
17
|
+
GraphQL::Schema.from_definition(schema)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
14
21
|
field_map = {}
|
15
22
|
resolver_map = {}
|
16
23
|
possible_locations = {}
|
24
|
+
visibility_profiles = if (visibility_def = schema.directives[GraphQL::Stitching.visibility_directive])
|
25
|
+
visibility_def.get_argument("profiles").default_value
|
26
|
+
else
|
27
|
+
[]
|
28
|
+
end
|
17
29
|
|
18
30
|
schema.types.each do |type_name, type|
|
19
31
|
next if type.introspection?
|
20
32
|
|
21
33
|
# Collect/build key definitions for each type
|
22
34
|
locations_by_key = type.directives.each_with_object({}) do |directive, memo|
|
23
|
-
next unless directive.graphql_name ==
|
35
|
+
next unless directive.graphql_name == Directives::SupergraphKey.graphql_name
|
24
36
|
|
25
37
|
kwargs = directive.arguments.keyword_arguments
|
26
38
|
memo[kwargs[:key]] ||= []
|
@@ -32,10 +44,10 @@ module GraphQL::Stitching
|
|
32
44
|
end
|
33
45
|
|
34
46
|
# Collect/build resolver definitions for each type
|
35
|
-
type.directives.each do |
|
36
|
-
next unless
|
47
|
+
type.directives.each do |d|
|
48
|
+
next unless d.graphql_name == Directives::SupergraphResolver.graphql_name
|
37
49
|
|
38
|
-
kwargs =
|
50
|
+
kwargs = d.arguments.keyword_arguments
|
39
51
|
resolver_map[type_name] ||= []
|
40
52
|
resolver_map[type_name] << TypeResolver.new(
|
41
53
|
location: kwargs[:location],
|
@@ -52,8 +64,8 @@ module GraphQL::Stitching
|
|
52
64
|
type.fields.each do |field_name, field|
|
53
65
|
# Collection locations for each field definition
|
54
66
|
field.directives.each do |d|
|
55
|
-
next unless d.graphql_name ==
|
56
|
-
|
67
|
+
next unless d.graphql_name == Directives::SupergraphSource.graphql_name
|
68
|
+
|
57
69
|
location = d.arguments.keyword_arguments[:location]
|
58
70
|
field_map[type_name] ||= {}
|
59
71
|
field_map[type_name][field_name] ||= []
|
@@ -74,6 +86,7 @@ module GraphQL::Stitching
|
|
74
86
|
schema: schema,
|
75
87
|
fields: field_map,
|
76
88
|
resolvers: resolver_map,
|
89
|
+
visibility_profiles: visibility_profiles,
|
77
90
|
executables: executables,
|
78
91
|
)
|
79
92
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL::Stitching
|
4
|
+
class Supergraph
|
5
|
+
module Visibility
|
6
|
+
def visible?(ctx)
|
7
|
+
profile = ctx[:visibility_profile]
|
8
|
+
return true if profile.nil?
|
9
|
+
|
10
|
+
directive = directives.find { _1.graphql_name == GraphQL::Stitching.visibility_directive }
|
11
|
+
return true if directive.nil?
|
12
|
+
|
13
|
+
profiles = directive.arguments.keyword_arguments[:profiles]
|
14
|
+
return true if profiles.nil?
|
15
|
+
|
16
|
+
profiles.include?(profile)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ArgumentType < GraphQL::Schema::Argument
|
21
|
+
include Visibility
|
22
|
+
end
|
23
|
+
|
24
|
+
class FieldType < GraphQL::Schema::Field
|
25
|
+
include Visibility
|
26
|
+
argument_class(ArgumentType)
|
27
|
+
end
|
28
|
+
|
29
|
+
class InputObjectType < GraphQL::Schema::InputObject
|
30
|
+
extend Visibility
|
31
|
+
argument_class(ArgumentType)
|
32
|
+
end
|
33
|
+
|
34
|
+
module InterfaceType
|
35
|
+
include GraphQL::Schema::Interface
|
36
|
+
field_class(FieldType)
|
37
|
+
|
38
|
+
definition_methods do
|
39
|
+
include Visibility
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ObjectType < GraphQL::Schema::Object
|
44
|
+
extend Visibility
|
45
|
+
field_class(FieldType)
|
46
|
+
end
|
47
|
+
|
48
|
+
class EnumValueType < GraphQL::Schema::EnumValue
|
49
|
+
include Visibility
|
50
|
+
end
|
51
|
+
|
52
|
+
class EnumType < GraphQL::Schema::Enum
|
53
|
+
extend Visibility
|
54
|
+
enum_value_class(EnumValueType)
|
55
|
+
end
|
56
|
+
|
57
|
+
class ScalarType < GraphQL::Schema::Scalar
|
58
|
+
extend Visibility
|
59
|
+
end
|
60
|
+
|
61
|
+
class UnionType < GraphQL::Schema::Union
|
62
|
+
extend Visibility
|
63
|
+
end
|
64
|
+
|
65
|
+
BASE_TYPES = {
|
66
|
+
enum: EnumType,
|
67
|
+
input_object: InputObjectType,
|
68
|
+
interface: InterfaceType,
|
69
|
+
object: ObjectType,
|
70
|
+
scalar: ScalarType,
|
71
|
+
union: UnionType,
|
72
|
+
}.freeze
|
73
|
+
end
|
74
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "supergraph/types"
|
3
4
|
require_relative "supergraph/from_definition"
|
4
5
|
|
5
6
|
module GraphQL
|
@@ -21,7 +22,7 @@ module GraphQL
|
|
21
22
|
attr_reader :memoized_introspection_types
|
22
23
|
attr_reader :locations_by_type_and_field
|
23
24
|
|
24
|
-
def initialize(schema:, fields: {}, resolvers: {}, executables: {})
|
25
|
+
def initialize(schema:, fields: {}, resolvers: {}, visibility_profiles: [], executables: {})
|
25
26
|
@schema = schema
|
26
27
|
@resolvers = resolvers
|
27
28
|
@resolvers_by_version = nil
|
@@ -49,11 +50,18 @@ module GraphQL
|
|
49
50
|
end
|
50
51
|
end.freeze
|
51
52
|
|
52
|
-
|
53
|
+
if visibility_profiles.any?
|
54
|
+
profiles = visibility_profiles.each_with_object({ nil => {} }) { |p, m| m[p.to_s] = {} }
|
55
|
+
@schema.use(GraphQL::Schema::Visibility, profiles: profiles)
|
56
|
+
else
|
57
|
+
@schema.use(GraphQL::Schema::AlwaysVisible)
|
58
|
+
end
|
53
59
|
end
|
54
60
|
|
55
|
-
def to_definition
|
56
|
-
@schema.to_definition
|
61
|
+
def to_definition(visibility_profile: nil)
|
62
|
+
@schema.to_definition(context: {
|
63
|
+
visibility_profile: visibility_profile,
|
64
|
+
}.tap(&:compact!))
|
57
65
|
end
|
58
66
|
|
59
67
|
def resolvers_by_version
|
data/lib/graphql/stitching.rb
CHANGED
@@ -32,8 +32,6 @@ module GraphQL
|
|
32
32
|
end
|
33
33
|
|
34
34
|
class << self
|
35
|
-
attr_writer :stitch_directive
|
36
|
-
|
37
35
|
# Proc used to compute digests; uses SHA2 by default.
|
38
36
|
# @returns [Proc] proc used to compute digests.
|
39
37
|
def digest(&block)
|
@@ -49,10 +47,31 @@ module GraphQL
|
|
49
47
|
def stitch_directive
|
50
48
|
@stitch_directive ||= "stitch"
|
51
49
|
end
|
50
|
+
|
51
|
+
attr_writer :stitch_directive
|
52
|
+
|
53
|
+
# Name of the directive used to denote member visibilities.
|
54
|
+
# @returns [String] name of the visibility directive.
|
55
|
+
def visibility_directive
|
56
|
+
@visibility_directive ||= "visibility"
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_writer :visibility_directive
|
60
|
+
|
61
|
+
MIN_VISIBILITY_VERSION = "2.5.3"
|
62
|
+
|
63
|
+
# @returns Boolean true if GraphQL::Schema::Visibility is fully supported
|
64
|
+
def supports_visibility?
|
65
|
+
return @supports_visibility if defined?(@supports_visibility)
|
66
|
+
|
67
|
+
# Requires `Visibility` (v2.4) with nil profile support (v2.5.3)
|
68
|
+
@supports_visibility = Gem::Version.new(GraphQL::VERSION) >= Gem::Version.new(MIN_VISIBILITY_VERSION)
|
69
|
+
end
|
52
70
|
end
|
53
71
|
end
|
54
72
|
end
|
55
73
|
|
74
|
+
require_relative "stitching/directives"
|
56
75
|
require_relative "stitching/supergraph"
|
57
76
|
require_relative "stitching/client"
|
58
77
|
require_relative "stitching/composer"
|
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.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg MacWilliam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -79,19 +79,20 @@ files:
|
|
79
79
|
- LICENSE
|
80
80
|
- README.md
|
81
81
|
- Rakefile
|
82
|
-
- docs/
|
83
|
-
- docs/
|
84
|
-
- docs/
|
85
|
-
- docs/federation_entities.md
|
86
|
-
- docs/http_executable.md
|
82
|
+
- docs/composing_a_supergraph.md
|
83
|
+
- docs/error_handling.md
|
84
|
+
- docs/executables.md
|
87
85
|
- docs/images/library.png
|
88
86
|
- docs/images/merging.png
|
89
87
|
- docs/images/stitching.png
|
90
|
-
- docs/
|
91
|
-
- docs/
|
88
|
+
- docs/introduction.md
|
89
|
+
- docs/merged_types.md
|
90
|
+
- docs/merged_types_apollo.md
|
91
|
+
- docs/performance.md
|
92
|
+
- docs/query_planning.md
|
93
|
+
- docs/serving_a_supergraph.md
|
92
94
|
- docs/subscriptions.md
|
93
|
-
- docs/
|
94
|
-
- docs/type_resolver.md
|
95
|
+
- docs/visibility.md
|
95
96
|
- examples/file_uploads/Gemfile
|
96
97
|
- examples/file_uploads/Procfile
|
97
98
|
- examples/file_uploads/README.md
|
@@ -161,10 +162,10 @@ files:
|
|
161
162
|
- lib/graphql/stitching/client.rb
|
162
163
|
- lib/graphql/stitching/composer.rb
|
163
164
|
- lib/graphql/stitching/composer/base_validator.rb
|
164
|
-
- lib/graphql/stitching/composer/supergraph_directives.rb
|
165
165
|
- lib/graphql/stitching/composer/type_resolver_config.rb
|
166
166
|
- lib/graphql/stitching/composer/validate_interfaces.rb
|
167
167
|
- lib/graphql/stitching/composer/validate_type_resolvers.rb
|
168
|
+
- lib/graphql/stitching/directives.rb
|
168
169
|
- lib/graphql/stitching/executor.rb
|
169
170
|
- lib/graphql/stitching/executor/root_source.rb
|
170
171
|
- lib/graphql/stitching/executor/shaper.rb
|
@@ -177,6 +178,7 @@ files:
|
|
177
178
|
- lib/graphql/stitching/request/skip_include.rb
|
178
179
|
- lib/graphql/stitching/supergraph.rb
|
179
180
|
- lib/graphql/stitching/supergraph/from_definition.rb
|
181
|
+
- lib/graphql/stitching/supergraph/types.rb
|
180
182
|
- lib/graphql/stitching/type_resolver.rb
|
181
183
|
- lib/graphql/stitching/type_resolver/arguments.rb
|
182
184
|
- lib/graphql/stitching/type_resolver/keys.rb
|
data/docs/README.md
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
## GraphQL::Stitching
|
2
|
-
|
3
|
-
This module provides a collection of components that may be composed into a stitched schema.
|
4
|
-
|
5
|
-

|
6
|
-
|
7
|
-
Major components include:
|
8
|
-
|
9
|
-
- [Client](./client.md) - an out-of-the-box setup for performing stitched requests.
|
10
|
-
- [Composer](./composer.md) - merges and validates many schemas into one graph.
|
11
|
-
- [Supergraph](./supergraph.md) - manages the combined schema and location routing maps. Can be exported, cached, and rehydrated.
|
12
|
-
- [Request](./request.md) - prepares a requested GraphQL document and variables for stitching.
|
13
|
-
- [HttpExecutable](./http_executable.md) - proxies requests to remotes with multipart file upload support.
|
14
|
-
|
15
|
-
Additional topics:
|
16
|
-
|
17
|
-
- [Stitching mechanics](./mechanics.md) - more about building for stitching and how it operates.
|
18
|
-
- [Subscriptions](./subscriptions.md) - explore how to stitch realtime event subscriptions.
|
19
|
-
- [Federation entities](./federation_entities.md) - more about Apollo Federation compatibility.
|
data/docs/client.md
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
## GraphQL::Stitching::Client
|
2
|
-
|
3
|
-
The `Client` is an out-of-the-box convenience with all stitching components assembled into a default workflow. A client is designed to work for most common needs, though you're welcome to assemble the component parts into your own configuration (see the [client source](../lib/graphql/stitching/client.rb) for an example). A client is constructed with the same [location settings](./composer.md#performing-composition) used to perform supergraph composition:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
movies_schema = "type Query { ..."
|
7
|
-
showtimes_schema = "type Query { ..."
|
8
|
-
|
9
|
-
client = GraphQL::Stitching::Client.new(locations: {
|
10
|
-
products: {
|
11
|
-
schema: GraphQL::Schema.from_definition(movies_schema),
|
12
|
-
executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3000"),
|
13
|
-
stitch: [{ field_name: "products", key: "id" }],
|
14
|
-
},
|
15
|
-
showtimes: {
|
16
|
-
schema: GraphQL::Schema.from_definition(showtimes_schema),
|
17
|
-
executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3001"),
|
18
|
-
},
|
19
|
-
my_local: {
|
20
|
-
schema: MyLocal::GraphQL::Schema,
|
21
|
-
},
|
22
|
-
})
|
23
|
-
```
|
24
|
-
|
25
|
-
Alternatively, you may pass a prebuilt `Supergraph` instance to the `Client` constructor. This is useful when [exporting and rehydrating](./supergraph.md#export-and-caching) supergraph instances, which bypasses the need for runtime composition:
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
supergraph_sdl = File.read("precomposed_schema.graphql")
|
29
|
-
supergraph = GraphQL::Stitching::Supergraph.from_definition(
|
30
|
-
supergraph_sdl,
|
31
|
-
executables: { ... },
|
32
|
-
)
|
33
|
-
|
34
|
-
client = GraphQL::Stitching::Client.new(supergraph: supergraph)
|
35
|
-
```
|
36
|
-
|
37
|
-
### Execution
|
38
|
-
|
39
|
-
A client provides an `execute` method with a subset of arguments provided by [`GraphQL::Schema.execute`](https://graphql-ruby.org/queries/executing_queries). Executing requests on a stitching client becomes mostly a drop-in replacement to executing on a `GraphQL::Schema` instance:
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
result = client.execute(
|
43
|
-
query: "query MyProduct($id: ID!) { product(id: $id) { name } }",
|
44
|
-
variables: { "id" => "1" },
|
45
|
-
operation_name: "MyProduct",
|
46
|
-
)
|
47
|
-
```
|
48
|
-
|
49
|
-
Arguments for the `execute` method include:
|
50
|
-
|
51
|
-
* `query`: a query (or mutation) as a string or parsed AST.
|
52
|
-
* `variables`: a hash of variables for the request.
|
53
|
-
* `operation_name`: the name of the operation to execute (when multiple are provided).
|
54
|
-
* `validate`: true if static validation should run on the supergraph schema before execution.
|
55
|
-
* `context`: an object passed through to executable calls and client hooks.
|
56
|
-
|
57
|
-
### Cache hooks
|
58
|
-
|
59
|
-
The client provides cache hooks to enable caching query plans across requests. Without caching, every request made to the client will be planned individually. With caching, a query may be planned once, cached, and then executed from cache for subsequent requests. Cache keys are a normalized digest of each query string.
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
client.on_cache_read do |request|
|
63
|
-
$cache.get(request.digest) # << 3P code
|
64
|
-
end
|
65
|
-
|
66
|
-
client.on_cache_write do |request, payload|
|
67
|
-
$cache.set(request.digest, payload) # << 3P code
|
68
|
-
end
|
69
|
-
```
|
70
|
-
|
71
|
-
All request digests use SHA2 by default. You can swap in [a faster algorithm](https://github.com/Shopify/blake3-rb) and/or add base scoping by reconfiguring the stitching library:
|
72
|
-
|
73
|
-
```ruby
|
74
|
-
GraphQL::Stitching.digest { |str| Digest::MD5.hexdigest("v2/#{str}") }
|
75
|
-
```
|
76
|
-
|
77
|
-
Note that inlined input data works against caching, so you should _avoid_ these input literals when possible:
|
78
|
-
|
79
|
-
```graphql
|
80
|
-
query {
|
81
|
-
product(id: "1") { name }
|
82
|
-
}
|
83
|
-
```
|
84
|
-
|
85
|
-
Instead, leverage query variables so that the document body remains consistent across requests:
|
86
|
-
|
87
|
-
```graphql
|
88
|
-
query($id: ID!) {
|
89
|
-
product(id: $id) { name }
|
90
|
-
}
|
91
|
-
|
92
|
-
# variables: { "id" => "1" }
|
93
|
-
```
|
94
|
-
|
95
|
-
### Error hooks
|
96
|
-
|
97
|
-
The client also provides an error hook. Any program errors rescued during execution will be passed to the `on_error` handler, which can report on the error as needed and return a formatted error message for the client to add to the [GraphQL errors](https://spec.graphql.org/June2018/#sec-Errors) result.
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
client.on_error do |request, err|
|
101
|
-
# log the error
|
102
|
-
Bugsnag.notify(err)
|
103
|
-
|
104
|
-
# return a formatted message for the public response
|
105
|
-
"Whoops, please contact support abount request '#{request.context[:request_id]}'"
|
106
|
-
end
|
107
|
-
```
|
data/docs/composer.md
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
## GraphQL::Stitching::Composer
|
2
|
-
|
3
|
-
A `Composer` receives many individual `GraphQL::Schema` instances representing various graph locations and _composes_ them into one combined [`Supergraph`](./supergraph.md) that is validated for integrity.
|
4
|
-
|
5
|
-
### Configuring composition
|
6
|
-
|
7
|
-
A `Composer` may be constructed with optional settings that tune how it builds a schema:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
composer = GraphQL::Stitching::Composer.new(
|
11
|
-
query_name: "Query",
|
12
|
-
mutation_name: "Mutation",
|
13
|
-
subscription_name: "Subscription",
|
14
|
-
description_merger: ->(values_by_location, info) { values_by_location.values.join("\n") },
|
15
|
-
deprecation_merger: ->(values_by_location, info) { values_by_location.values.first },
|
16
|
-
default_value_merger: ->(values_by_location, info) { values_by_location.values.first },
|
17
|
-
directive_kwarg_merger: ->(values_by_location, info) { values_by_location.values.last },
|
18
|
-
root_field_location_selector: ->(locations, info) { locations.last },
|
19
|
-
)
|
20
|
-
```
|
21
|
-
|
22
|
-
Constructor arguments:
|
23
|
-
|
24
|
-
- **`query_name:`** _optional_, the name of the root query type in the composed schema; `Query` by default. The root query types from all location schemas will be merged into this type, regardless of their local names.
|
25
|
-
|
26
|
-
- **`mutation_name:`** _optional_, the name of the root mutation type in the composed schema; `Mutation` by default. The root mutation types from all location schemas will be merged into this type, regardless of their local names.
|
27
|
-
|
28
|
-
- **`subscription_name:`** _optional_, the name of the root subscription type in the composed schema; `Subscription` by default. The root subscription types from all location schemas will be merged into this type, regardless of their local names.
|
29
|
-
|
30
|
-
- **`description_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging element description strings from across locations.
|
31
|
-
|
32
|
-
- **`deprecation_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging element deprecation strings from across locations.
|
33
|
-
|
34
|
-
- **`default_value_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging argument default values from across locations.
|
35
|
-
|
36
|
-
- **`directive_kwarg_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging directive keyword arguments from across locations.
|
37
|
-
|
38
|
-
- **`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.
|
39
|
-
|
40
|
-
#### Value merger functions
|
41
|
-
|
42
|
-
Static data values such as element descriptions and directive arguments must also merge across locations. By default, the first non-null value encountered for a given element attribute is used. A value merger function may customize this process by selecting a different value or computing a new one:
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
composer = GraphQL::Stitching::Composer.new(
|
46
|
-
description_merger: ->(values_by_location, info) { values_by_location.values.compact.join("\n") },
|
47
|
-
)
|
48
|
-
```
|
49
|
-
|
50
|
-
A merger function receives `values_by_location` and `info` arguments; these provide possible values keyed by location and info about where in the schema these values were encountered:
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
values_by_location = {
|
54
|
-
"storefronts" => "A fabulous data type.",
|
55
|
-
"products" => "An excellent data type.",
|
56
|
-
}
|
57
|
-
|
58
|
-
info = {
|
59
|
-
type_name: "Product",
|
60
|
-
# field_name: ...,
|
61
|
-
# argument_name: ...,
|
62
|
-
# directive_name: ...,
|
63
|
-
}
|
64
|
-
```
|
65
|
-
|
66
|
-
### Performing composition
|
67
|
-
|
68
|
-
Construct a `Composer` and call its `perform` method with location settings to compose a supergraph:
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
storefronts_sdl = "type Query { ..."
|
72
|
-
products_sdl = "type Query { ..."
|
73
|
-
|
74
|
-
supergraph = GraphQL::Stitching::Composer.new.perform({
|
75
|
-
storefronts: {
|
76
|
-
schema: GraphQL::Schema.from_definition(storefronts_sdl),
|
77
|
-
executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3001"),
|
78
|
-
stitch: [{ field_name: "storefront", key: "id" }],
|
79
|
-
},
|
80
|
-
products: {
|
81
|
-
schema: GraphQL::Schema.from_definition(products_sdl),
|
82
|
-
executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3002"),
|
83
|
-
},
|
84
|
-
my_local: {
|
85
|
-
schema: MyLocalSchema,
|
86
|
-
},
|
87
|
-
})
|
88
|
-
|
89
|
-
combined_schema = supergraph.schema
|
90
|
-
```
|
91
|
-
|
92
|
-
Location settings have top-level keys that specify arbitrary location names, each of which provide:
|
93
|
-
|
94
|
-
- **`schema:`** _required_, provides a `GraphQL::Schema` class for the location. This may be a class-based schema that inherits from `GraphQL::Schema`, or built from SDL (Schema Definition Language) string using `GraphQL::Schema.from_definition` and mapped to a remote location. The provided schema is only used for type reference and does not require any real data resolvers (unless it is also used as the location's executable, see below).
|
95
|
-
|
96
|
-
- **`executable:`** _optional_, provides an executable resource to be called when delegating a request to this location. Executables are `GraphQL::Schema` classes or any object with a `.call(request, source, variables)` method that returns a GraphQL response. Omitting the executable option will use the location's provided `schema` as the executable resource.
|
97
|
-
|
98
|
-
- **`stitch:`** _optional_, an array of configs used to dynamically apply `@stitch` directives to select root fields prior to composing. This is useful when you can't easily render stitching directives into a location's source schema.
|
99
|
-
|
100
|
-
### Merge patterns
|
101
|
-
|
102
|
-
The strategy used to merge source schemas into the combined schema is based on each element type:
|
103
|
-
|
104
|
-
- Arguments of fields, directives, and `InputObject` types intersect for each parent element across locations (an element's arguments must appear in all locations):
|
105
|
-
- Arguments must share a value type, and the strictest nullability across locations is used.
|
106
|
-
- Composition fails if argument intersection would eliminate a non-null argument.
|
107
|
-
|
108
|
-
- `Object` and `Interface` types merge their fields and directives together:
|
109
|
-
- Common fields across locations must share a value type, and the weakest nullability is used.
|
110
|
-
- Objects with unique fields across locations must implement [`@stitch` accessors](../README.md#merged-types).
|
111
|
-
- Shared object types without `@stitch` accessors must contain identical fields.
|
112
|
-
- Merged interfaces must remain compatible with all underlying implementations.
|
113
|
-
|
114
|
-
- `Enum` types merge their values based on how the enum is used:
|
115
|
-
- Enums used anywhere as an argument will intersect their values (common values across all locations).
|
116
|
-
- Enums used exclusively in read contexts will provide a union of values (all values across all locations).
|
117
|
-
|
118
|
-
- `Union` types merge all possible types from across all locations.
|
119
|
-
|
120
|
-
- `Scalar` types are added for all scalar names across all locations.
|
121
|
-
|
122
|
-
- `Directive` definitions are added for all distinct names across locations:
|
123
|
-
- Stitching directives (both definitions and assignments) are omitted.
|
124
|
-
|
125
|
-
Note that the structure of a composed schema may change based on new schema additions and/or element usage (ie: changing input object arguments in one service may cause the intersection of arguments to change). Therefore, it's highly recommended that you use a [schema comparator](https://github.com/xuorig/graphql-schema_comparator) to flag regressions across composed schema versions.
|
data/docs/http_executable.md
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
## GraphQL::Stitching::HttpExecutable
|
2
|
-
|
3
|
-
A `HttpExecutable` provides an out-of-the-box convenience for sending HTTP post requests to a remote location, or a base class for your own implementation with [GraphQL multipart uploads](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
exe = GraphQL::Stitching::HttpExecutable.new(
|
7
|
-
url: "http://localhost:3001",
|
8
|
-
headers: {
|
9
|
-
"Authorization" => "..."
|
10
|
-
}
|
11
|
-
)
|
12
|
-
```
|
13
|
-
|
14
|
-
### GraphQL file uploads
|
15
|
-
|
16
|
-
The [GraphQL Upload Spec](https://github.com/jaydenseric/graphql-multipart-request-spec) defines a multipart form structure for submitting GraphQL requests with file upload attachments. It's possible to pass these requests through stitched schemas using the following:
|
17
|
-
|
18
|
-
#### 1. Input file uploads as Tempfile variables
|
19
|
-
|
20
|
-
```ruby
|
21
|
-
client.execute(
|
22
|
-
"mutation($file: Upload) { upload(file: $file) }",
|
23
|
-
variables: { "file" => Tempfile.new(...) }
|
24
|
-
)
|
25
|
-
```
|
26
|
-
|
27
|
-
File uploads must enter the stitched schema as standard GraphQL variables with `Tempfile` values. The simplest way to recieve this input is to install [apollo_upload_server](https://github.com/jetruby/apollo_upload_server-ruby) into your stitching app's middleware so that multipart form submissions automatically unpack into standard variables.
|
28
|
-
|
29
|
-
#### 2. Enable `HttpExecutable.upload_types`
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
client = GraphQL::Stitching::Client.new(locations: {
|
33
|
-
alpha: {
|
34
|
-
schema: GraphQL::Schema.from_definition(...),
|
35
|
-
executable: GraphQL::Stitching::HttpExecutable.new(
|
36
|
-
url: "http://localhost:3000",
|
37
|
-
upload_types: ["Upload"], # << extract `Upload` scalars into multipart forms
|
38
|
-
),
|
39
|
-
},
|
40
|
-
bravo: {
|
41
|
-
schema: GraphQL::Schema.from_definition(...),
|
42
|
-
executable: GraphQL::Stitching::HttpExecutable.new(
|
43
|
-
url: "http://localhost:3001"
|
44
|
-
),
|
45
|
-
},
|
46
|
-
})
|
47
|
-
```
|
48
|
-
|
49
|
-
A location's `HttpExecutable` can then re-package `Tempfile` variables into multipart forms before sending them upstream. This is enabled with an `upload_types` parameter that specifies which scalar names require form extraction. Enabling `upload_types` does add some additional subgraph request processing, so it should only be enabled for locations that will actually recieve file uploads.
|
50
|
-
|
51
|
-
The upstream location will recieve a multipart form submission from stitching that can again be unpacked using [apollo_upload_server](https://github.com/jetruby/apollo_upload_server-ruby) or similar.
|