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.
@@ -10,17 +10,29 @@ module GraphQL::Stitching
10
10
  end
11
11
 
12
12
  def from_definition(schema, executables:)
13
- schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
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 == Composer::KeyDirective.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 |directive|
36
- next unless directive.graphql_name == Composer::ResolverDirective.graphql_name
47
+ type.directives.each do |d|
48
+ next unless d.graphql_name == Directives::SupergraphResolver.graphql_name
37
49
 
38
- kwargs = directive.arguments.keyword_arguments
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 == Composer::SourceDirective.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
- @schema.use(GraphQL::Schema::AlwaysVisible)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Stitching
5
- VERSION = "1.6.2"
5
+ VERSION = "1.7.1"
6
6
  end
7
7
  end
@@ -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.6.2
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-04-26 00:00:00.000000000 Z
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/README.md
83
- - docs/client.md
84
- - docs/composer.md
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/mechanics.md
91
- - docs/request.md
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/supergraph.md
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
- ![Library flow](./images/library.png)
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.
@@ -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.