graphql-stitching 1.1.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -0
  3. data/Gemfile +0 -3
  4. data/README.md +10 -32
  5. data/docs/README.md +1 -0
  6. data/docs/client.md +2 -2
  7. data/docs/composer.md +1 -1
  8. data/docs/executor.md +8 -15
  9. data/docs/http_executable.md +51 -0
  10. data/docs/planner.md +12 -14
  11. data/docs/request.md +2 -0
  12. data/docs/supergraph.md +2 -2
  13. data/examples/file_uploads/Gemfile +9 -0
  14. data/examples/file_uploads/Procfile +2 -0
  15. data/examples/file_uploads/README.md +37 -0
  16. data/examples/file_uploads/file.txt +1 -0
  17. data/examples/file_uploads/gateway.rb +37 -0
  18. data/examples/file_uploads/helpers.rb +62 -0
  19. data/examples/file_uploads/remote.rb +21 -0
  20. data/examples/merged_types/Gemfile +8 -0
  21. data/examples/merged_types/Procfile +3 -0
  22. data/examples/merged_types/README.md +33 -0
  23. data/{example → examples/merged_types}/gateway.rb +4 -5
  24. data/examples/merged_types/remote1.rb +22 -0
  25. data/examples/merged_types/remote2.rb +22 -0
  26. data/lib/graphql/stitching/client.rb +9 -19
  27. data/lib/graphql/stitching/composer/base_validator.rb +3 -3
  28. data/lib/graphql/stitching/composer/validate_boundaries.rb +3 -3
  29. data/lib/graphql/stitching/composer/validate_interfaces.rb +3 -4
  30. data/lib/graphql/stitching/composer.rb +66 -9
  31. data/lib/graphql/stitching/executor/boundary_source.rb +4 -6
  32. data/lib/graphql/stitching/executor/root_source.rb +4 -4
  33. data/lib/graphql/stitching/executor.rb +16 -13
  34. data/lib/graphql/stitching/export_selection.rb +6 -1
  35. data/lib/graphql/stitching/http_executable.rb +145 -4
  36. data/lib/graphql/stitching/planner.rb +3 -3
  37. data/lib/graphql/stitching/request.rb +66 -4
  38. data/lib/graphql/stitching/shaper.rb +4 -3
  39. data/lib/graphql/stitching/skip_include.rb +4 -3
  40. data/lib/graphql/stitching/supergraph/resolver_directive.rb +17 -0
  41. data/lib/graphql/stitching/supergraph/source_directive.rb +12 -0
  42. data/lib/graphql/stitching/supergraph.rb +27 -34
  43. data/lib/graphql/stitching/util.rb +0 -9
  44. data/lib/graphql/stitching/version.rb +1 -1
  45. metadata +20 -7
  46. data/Procfile +0 -3
  47. data/example/remote1.rb +0 -26
  48. data/example/remote2.rb +0 -26
  49. /data/{example → examples/merged_types}/graphiql.html +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cdcb3361e391ba9b6dd78aa38c308c08ddb6fde2720d15208e8dfb96c3006ea
4
- data.tar.gz: 9f64b024bda3987d1c523cf52b49f0ca2dfd688ac4c89bbf18de30c88c098156
3
+ metadata.gz: 4cb8c0d3b16cda5db8b82d0d0f9eba93a4537c3faa584b1b301dc84b34b5fd0e
4
+ data.tar.gz: 83f4b436479307ac8575f718ace5a82288024a6e5741f786151e55920d7ef61f
5
5
  SHA512:
6
- metadata.gz: b51bcfeaa20e9cdea6c2d4b4e472e8c23e13ec8af36e1b23852603d0329045af064e3ae738467a0823be0c9f545105acba001b708c207eb7f08d51ebf0791e1f
7
- data.tar.gz: c529e74c88fc7193d7823d8c92a488b3e459b5ba9f88e8d62355ae2bd5e9eb0ed2b0d6f7fd14b6b8ed13ab1bb53a93bc5da49dcecd5deb92ee180fa70604c80b
6
+ metadata.gz: e247e539e223a1fbefcd398c1aeb3940fa91320b81c45caeaa21b3ddca50631ffb6ddbd3963cde07f11609cbaacc1f814a167fefebab8f888979afe929bcb93f
7
+ data.tar.gz: 66de3bacb73749bd90b4d8e7ade4bf35cc144e0ae60b3ff1c81a5f548b20d74e204454b6727875984da293b4b3853aa9bf6215283666f2b037a7dc148e8aa64b
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --no-private
2
+ --markup=markdown
3
+ --readme=readme.md
4
+ --title='GraphQL Stitching Ruby API Documentation'
5
+ 'lib/**/*.rb' - '*.md'
data/Gemfile CHANGED
@@ -3,9 +3,6 @@
3
3
  source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
- gem 'rack'
7
- gem 'rackup'
8
- gem 'foreman'
9
6
  gem 'pry'
10
7
  gem 'pry-byebug'
11
8
  gem 'warning'
data/README.md CHANGED
@@ -10,6 +10,7 @@ GraphQL stitching composes a single schema from multiple underlying GraphQL reso
10
10
  - Shared objects, fields, enums, and inputs across locations.
11
11
  - Combining local and remote schemas.
12
12
  - Type merging via arbitrary queries or federation `_entities` protocol.
13
+ - File uploads via [multipart form spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
13
14
 
14
15
  **NOT Supported:**
15
16
  - Computed fields (ie: federation-style `@requires`).
@@ -80,6 +81,7 @@ While the `Client` constructor is an easy quick start, the library also has seve
80
81
  - [Request](./docs/request.md) - prepares a requested GraphQL document and variables for stitching.
81
82
  - [Planner](./docs/planner.md) - builds a cacheable query plan for a request document.
82
83
  - [Executor](./docs/executor.md) - executes a query plan with given request variables.
84
+ - [HttpExecutable](./docs/http_executable.md) - proxies requests to remotes with multipart file upload support.
83
85
 
84
86
  ## Merged types
85
87
 
@@ -360,11 +362,11 @@ It's perfectly fine to mix and match schemas that implement an `_entities` query
360
362
 
361
363
  ## Executables
362
364
 
363
- An executable resource performs location-specific GraphQL requests. Executables may be `GraphQL::Schema` classes, or any object that responds to `.call(location, source, variables, context)` and returns a raw GraphQL response:
365
+ An executable resource performs location-specific GraphQL requests. Executables may be `GraphQL::Schema` classes, or any object that responds to `.call(request, source, variables)` and returns a raw GraphQL response:
364
366
 
365
367
  ```ruby
366
368
  class MyExecutable
367
- def call(location, source, variables, context)
369
+ def call(request, source, variables)
368
370
  # process a GraphQL request...
369
371
  return {
370
372
  "data" => { ... },
@@ -392,12 +394,12 @@ supergraph = GraphQL::Stitching::Composer.new.perform({
392
394
  },
393
395
  fourth: {
394
396
  schema: FourthSchema,
395
- executable: ->(loc, query, vars, ctx) { ... },
397
+ executable: ->(req, query, vars) { ... },
396
398
  },
397
399
  })
398
400
  ```
399
401
 
400
- The `GraphQL::Stitching::HttpExecutable` class is provided as a simple executable wrapper around `Net::HTTP.post`. You should build your own executables to leverage your existing libraries and to add instrumentation. Note that you must manually assign all executables to a `Supergraph` when rehydrating it from cache ([see docs](./docs/supergraph.md)).
402
+ The `GraphQL::Stitching::HttpExecutable` class is provided as a simple executable wrapper around `Net::HTTP.post` with [file upload](./docs/http_executable.md#graphql-file-uploads) support. You should build your own executables to leverage your existing libraries and to add instrumentation. Note that you must manually assign all executables to a `Supergraph` when rehydrating it from cache ([see docs](./docs/supergraph.md)).
401
403
 
402
404
  ## Batching
403
405
 
@@ -431,36 +433,12 @@ The [Executor](./docs/executor.md) component builds atop the Ruby fiber-based im
431
433
  - [Stitched errors](./docs/mechanics.md#stitched-errors)
432
434
  - [Null results](./docs/mechanics.md#null-results)
433
435
 
434
- ## Example
436
+ ## Examples
435
437
 
436
- This repo includes a working example of several stitched schemas running across small Rack servers. Try running it:
438
+ This repo includes working examples of stitched schemas running across small Rack servers. Clone the repo, `cd` into each example and try running it following its README instructions.
437
439
 
438
- ```shell
439
- bundle install
440
- foreman start
441
- ```
442
-
443
- Then visit the gateway service at `http://localhost:3000` and try this query:
444
-
445
- ```graphql
446
- query {
447
- storefront(id: "1") {
448
- id
449
- products {
450
- upc
451
- name
452
- price
453
- manufacturer {
454
- name
455
- address
456
- products { upc name }
457
- }
458
- }
459
- }
460
- }
461
- ```
462
-
463
- The above query collects data from all locations, two of which are remote schemas and the third a local schema. The combined graph schema is also stitched in to provide introspection capabilities.
440
+ - [Merged types](./examples/merged_types)
441
+ - [File uploads](./examples/file_uploads)
464
442
 
465
443
  ## Tests
466
444
 
data/docs/README.md CHANGED
@@ -12,6 +12,7 @@ Major components include:
12
12
  - [Request](./request.md) - prepares a requested GraphQL document and variables for stitching.
13
13
  - [Planner](./planner.md) - builds a cacheable query plan for a request document.
14
14
  - [Executor](./executor.md) - executes a query plan with given request variables.
15
+ - [HttpExecutable](./http_executable.md) - proxies requests to remotes with multipart file upload support.
15
16
 
16
17
  Additional topics:
17
18
 
data/docs/client.md CHANGED
@@ -60,11 +60,11 @@ The client provides cache hooks to enable caching query plans across requests. W
60
60
 
61
61
  ```ruby
62
62
  client.on_cache_read do |request|
63
- $redis.get(request.digest) # << 3P code
63
+ $cache.get(request.digest) # << 3P code
64
64
  end
65
65
 
66
66
  client.on_cache_write do |request, payload|
67
- $redis.set(request.digest, payload) # << 3P code
67
+ $cache.set(request.digest, payload) # << 3P code
68
68
  end
69
69
  ```
70
70
 
data/docs/composer.md CHANGED
@@ -90,7 +90,7 @@ Location settings have top-level keys that specify arbitrary location names, eac
90
90
 
91
91
  - **`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).
92
92
 
93
- - **`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(location, source, variables, context)` method that returns a GraphQL response. Omitting the executable option will use the location's provided `schema` as the executable resource.
93
+ - **`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.
94
94
 
95
95
  - **`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.
96
96
 
data/docs/executor.md CHANGED
@@ -13,22 +13,18 @@ query = <<~GRAPHQL
13
13
  GRAPHQL
14
14
 
15
15
  request = GraphQL::Stitching::Request.new(
16
+ supergraph,
16
17
  query,
17
18
  variables: { "id" => "123" },
18
19
  operation_name: "MyQuery",
19
20
  context: { ... },
20
21
  )
21
22
 
22
- plan = GraphQL::Stitching::Planner.new(
23
- supergraph: supergraph,
24
- request: request,
25
- ).perform
23
+ # Via Request:
24
+ result = request.execute
26
25
 
27
- result = GraphQL::Stitching::Executor.new(
28
- supergraph: supergraph,
29
- request: request,
30
- plan: plan,
31
- ).perform
26
+ # Via Executor:
27
+ result = GraphQL::Stitching::Executor.new(request).perform
32
28
  ```
33
29
 
34
30
  ### Raw results
@@ -36,12 +32,9 @@ result = GraphQL::Stitching::Executor.new(
36
32
  By default, execution results are always returned with document shaping (stitching additions removed, missing fields added, null bubbling applied). You may access the raw execution result by calling the `perform` method with a `raw: true` argument:
37
33
 
38
34
  ```ruby
39
- # get the raw result without shaping
40
- raw_result = GraphQL::Stitching::Executor.new(
41
- supergraph: supergraph,
42
- request: request,
43
- plan: plan,
44
- ).perform(raw: true)
35
+ # get the raw result without shaping using either form:
36
+ raw_result = request.execute(raw: true)
37
+ raw_result = GraphQL::Stitching::Executor.new(request).perform(raw: true)
45
38
  ```
46
39
 
47
40
  The raw result will contain many irregularities from the stitching process, however may be insightful when debugging inconsistencies in results:
@@ -0,0 +1,51 @@
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.
data/docs/planner.md CHANGED
@@ -13,15 +13,17 @@ document = <<~GRAPHQL
13
13
  GRAPHQL
14
14
 
15
15
  request = GraphQL::Stitching::Request.new(
16
+ supergraph,
16
17
  document,
17
18
  variables: { "id" => "1" },
18
19
  operation_name: "MyQuery",
19
20
  ).prepare!
20
21
 
21
- plan = GraphQL::Stitching::Planner.new(
22
- supergraph: supergraph,
23
- request: request,
24
- ).perform
22
+ # Via Request:
23
+ plan = request.plan
24
+
25
+ # Via Planner:
26
+ plan = GraphQL::Stitching::Planner.new(request).perform
25
27
  ```
26
28
 
27
29
  ### Caching
@@ -29,18 +31,14 @@ plan = GraphQL::Stitching::Planner.new(
29
31
  Plans are designed to be cacheable. This is very useful for redundant GraphQL documents (commonly sent by frontend clients) where there's no sense in planning every request individually. It's far more efficient to generate a plan once and cache it, then simply retreive the plan and execute it for future requests.
30
32
 
31
33
  ```ruby
32
- cached_plan = $redis.get(request.digest)
34
+ cached_plan = $cache.get(request.digest)
33
35
 
34
- plan = if cached_plan
35
- GraphQL::Stitching::Plan.from_json(JSON.parse(cached_plan))
36
+ if cached_plan
37
+ plan = GraphQL::Stitching::Plan.from_json(JSON.parse(cached_plan))
38
+ request.plan(plan)
36
39
  else
37
- plan = GraphQL::Stitching::Planner.new(
38
- supergraph: supergraph,
39
- request: request,
40
- ).perform
41
-
42
- $redis.set(request.digest, JSON.generate(plan.as_json))
43
- plan
40
+ plan = request.plan
41
+ $cache.set(request.digest, JSON.generate(plan.as_json))
44
42
  end
45
43
 
46
44
  # execute the plan...
data/docs/request.md CHANGED
@@ -5,6 +5,7 @@ A `Request` contains a parsed GraphQL document and variables, and handles the lo
5
5
  ```ruby
6
6
  source = "query FetchMovie($id: ID!) { movie(id:$id) { id genre } }"
7
7
  request = GraphQL::Stitching::Request.new(
8
+ supergraph,
8
9
  source,
9
10
  variables: { "id" => "1" },
10
11
  operation_name: "FetchMovie",
@@ -42,6 +43,7 @@ document = <<~GRAPHQL
42
43
  GRAPHQL
43
44
 
44
45
  request = GraphQL::Stitching::Request.new(
46
+ supergraph,
45
47
  document,
46
48
  variables: { "id" => "1" },
47
49
  operation_name: "FetchMovie",
data/docs/supergraph.md CHANGED
@@ -10,7 +10,7 @@ A Supergraph is designed to be composed, cached, and restored. Calling `to_defin
10
10
  supergraph_sdl = supergraph.to_definition
11
11
 
12
12
  # stash this composed schema in a cache...
13
- $redis.set("cached_supergraph_sdl", supergraph_sdl)
13
+ $cache.set("cached_supergraph_sdl", supergraph_sdl)
14
14
 
15
15
  # or, write the composed schema as a file into your repo...
16
16
  File.write("supergraph/schema.graphql", supergraph_sdl)
@@ -19,7 +19,7 @@ File.write("supergraph/schema.graphql", supergraph_sdl)
19
19
  To restore a Supergraph, call `from_definition` providing the cached SDL string and a hash of executables keyed by their location names:
20
20
 
21
21
  ```ruby
22
- supergraph_sdl = $redis.get("cached_supergraph_sdl")
22
+ supergraph_sdl = $cache.get("cached_supergraph_sdl")
23
23
 
24
24
  supergraph = GraphQL::Stitching::Supergraph.from_definition(
25
25
  supergraph_sdl,
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rack'
6
+ gem 'rackup'
7
+ gem 'foreman'
8
+ gem 'graphql'
9
+ gem 'apollo_upload_server', '2.1'
@@ -0,0 +1,2 @@
1
+ gateway: bundle exec ruby gateway.rb
2
+ remote: bundle exec ruby remote.rb
@@ -0,0 +1,37 @@
1
+ # File uploads example
2
+
3
+ This example demonstrates uploading files via the [GraphQL Upload spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
4
+
5
+ Try running it:
6
+
7
+ ```shell
8
+ cd examples/file_uploads
9
+ bundle install
10
+ foreman start
11
+ ```
12
+
13
+ This example is headless, but you can verify the stitched schema is running by querying a field from each graph location:
14
+
15
+ ```shell
16
+ curl -X POST http://localhost:3000 \
17
+ -H 'Content-Type: application/json' \
18
+ -d '{"query":"{ gateway remote }"}'
19
+ ```
20
+
21
+ Now try submitting a multipart form upload with a file attachment, per the [spec](https://github.com/jaydenseric/graphql-multipart-request-spec?tab=readme-ov-file#curl-request). The response will echo the uploaded file contents:
22
+
23
+ ```shell
24
+ curl http://localhost:3000 \
25
+ -H 'Content-Type: multipart/form-data' \
26
+ -F operations='{ "query": "mutation ($file: Upload!) { gateway upload(file: $file) }", "variables": { "file": null } }' \
27
+ -F map='{ "0": ["variables.file"] }' \
28
+ -F 0=@file.txt
29
+ ```
30
+
31
+ This workflow has:
32
+
33
+ 1. Submitted a multipart form to the stitched gateway.
34
+ 2. The gateway server unpacked the request using [apollo_upload_server](https://github.com/jetruby/apollo_upload_server-ruby).
35
+ 3. Stitching delegated the `upload` field to its appropraite subgraph location.
36
+ 4. `HttpExecutable` has re-encoded the subgraph request into a multipart form.
37
+ 5. The subgraph location has recieved, unpacked, and resolved the uploaded file.
@@ -0,0 +1 @@
1
+ Hello World!
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+ require 'json'
5
+ require 'graphql'
6
+ require_relative '../../lib/graphql/stitching'
7
+ require_relative './helpers'
8
+
9
+ class StitchedApp
10
+ def initialize
11
+ @client = GraphQL::Stitching::Client.new(locations: {
12
+ gateway: {
13
+ schema: GatewaySchema,
14
+ },
15
+ remote: {
16
+ schema: RemoteSchema,
17
+ executable: GraphQL::Stitching::HttpExecutable.new(
18
+ url: "http://localhost:3001",
19
+ upload_types: ["Upload"]
20
+ ),
21
+ },
22
+ })
23
+ end
24
+
25
+ def call(env)
26
+ params = apollo_upload_server_middleware_params(env)
27
+ result = @client.execute(
28
+ query: params["query"],
29
+ variables: params["variables"],
30
+ operation_name: params["operationName"],
31
+ )
32
+
33
+ [200, {"content-type" => "application/json"}, [JSON.generate(result)]]
34
+ end
35
+ end
36
+
37
+ Rackup::Handler.default.run(StitchedApp.new, :Port => 3000)
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require 'apollo_upload_server/graphql_data_builder'
5
+ require 'apollo_upload_server/upload'
6
+
7
+ # ApolloUploadServer middleware only modifies Rails request params;
8
+ # for simple Rack apps we need to extract the behavior.
9
+ def apollo_upload_server_middleware_params(env)
10
+ req = ActionDispatch::Request.new(env)
11
+ if env['CONTENT_TYPE'].to_s.include?('multipart/form-data')
12
+ ApolloUploadServer::GraphQLDataBuilder.new(strict_mode: true).call(req.params)
13
+ else
14
+ req.params
15
+ end
16
+ end
17
+
18
+ # Gateway local schema
19
+ class GatewaySchema < GraphQL::Schema
20
+ class Query < GraphQL::Schema::Object
21
+ field :gateway, Boolean, null: false
22
+
23
+ def gateway
24
+ true
25
+ end
26
+ end
27
+
28
+ class Mutation < GraphQL::Schema::Object
29
+ field :gateway, Boolean, null: false
30
+
31
+ def gateway
32
+ true
33
+ end
34
+ end
35
+
36
+ query Query
37
+ mutation Mutation
38
+ end
39
+
40
+ # Remote local schema, with file upload
41
+ class RemoteSchema < GraphQL::Schema
42
+ class Query < GraphQL::Schema::Object
43
+ field :remote, Boolean, null: false
44
+
45
+ def remote
46
+ true
47
+ end
48
+ end
49
+
50
+ class Mutation < GraphQL::Schema::Object
51
+ field :upload, String, null: true do
52
+ argument :file, ApolloUploadServer::Upload, required: true
53
+ end
54
+
55
+ def upload(file:)
56
+ file.read
57
+ end
58
+ end
59
+
60
+ query Query
61
+ mutation Mutation
62
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+ require 'json'
5
+ require 'graphql'
6
+ require_relative './helpers'
7
+
8
+ class RemoteApp
9
+ def call(env)
10
+ params = apollo_upload_server_middleware_params(env)
11
+ result = RemoteSchema.execute(
12
+ query: params["query"],
13
+ variables: params["variables"],
14
+ operation_name: params["operationName"],
15
+ )
16
+
17
+ [200, {"content-type" => "application/json"}, [JSON.generate(result)]]
18
+ end
19
+ end
20
+
21
+ Rackup::Handler.default.run(RemoteApp.new, :Port => 3001)
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rack'
6
+ gem 'rackup'
7
+ gem 'foreman'
8
+ gem 'graphql'
@@ -0,0 +1,3 @@
1
+ gateway: bundle exec ruby gateway.rb
2
+ remote1: bundle exec ruby remote1.rb
3
+ remote2: bundle exec ruby remote2.rb
@@ -0,0 +1,33 @@
1
+ # Merged types example
2
+
3
+ This example demonstrates several stitched schemas running across small Rack servers with types merged across locations. The main "gateway" location stitches its local schema onto two remote endpoints.
4
+
5
+ Try running it:
6
+
7
+ ```shell
8
+ cd examples/merged_types
9
+ bundle install
10
+ foreman start
11
+ ```
12
+
13
+ Then visit the gateway service at [`http://localhost:3000`](http://localhost:3000) and try this query:
14
+
15
+ ```graphql
16
+ query {
17
+ storefront(id: "1") {
18
+ id
19
+ products {
20
+ upc
21
+ name
22
+ price
23
+ manufacturer {
24
+ name
25
+ address
26
+ products { upc name }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ The above query collects data from all locations. You can also request introspections that resolve using the combined supergraph schema.
@@ -2,10 +2,9 @@
2
2
 
3
3
  require 'rackup'
4
4
  require 'json'
5
- require 'byebug'
6
5
  require 'graphql'
7
- require 'graphql/stitching'
8
- require_relative '../test/schemas/example'
6
+ require_relative '../../lib/graphql/stitching'
7
+ require_relative '../../test/schemas/example'
9
8
 
10
9
  class StitchedApp
11
10
  def initialize
@@ -19,11 +18,11 @@ class StitchedApp
19
18
  },
20
19
  storefronts: {
21
20
  schema: Schemas::Example::Storefronts,
22
- executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3001/graphql"),
21
+ executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3001"),
23
22
  },
24
23
  manufacturers: {
25
24
  schema: Schemas::Example::Manufacturers,
26
- executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3002/graphql"),
25
+ executable: GraphQL::Stitching::HttpExecutable.new(url: "http://localhost:3002"),
27
26
  }
28
27
  })
29
28
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+ require 'json'
5
+ require 'graphql'
6
+ require_relative '../../test/schemas/example'
7
+
8
+ class FirstRemoteApp
9
+ def call(env)
10
+ req = Rack::Request.new(env)
11
+ params = JSON.parse(req.body.read)
12
+ result = Schemas::Example::Storefronts.execute(
13
+ query: params["query"],
14
+ variables: params["variables"],
15
+ operation_name: params["operationName"],
16
+ )
17
+
18
+ [200, {"content-type" => "application/json"}, [JSON.generate(result)]]
19
+ end
20
+ end
21
+
22
+ Rackup::Handler.default.run(FirstRemoteApp.new, :Port => 3001)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+ require 'json'
5
+ require 'graphql'
6
+ require_relative '../../test/schemas/example'
7
+
8
+ class SecondRemoteApp
9
+ def call(env)
10
+ req = Rack::Request.new(env)
11
+ params = JSON.parse(req.body.read)
12
+ result = Schemas::Example::Manufacturers.execute(
13
+ query: params["query"],
14
+ variables: params["variables"],
15
+ operation_name: params["operationName"],
16
+ )
17
+
18
+ [200, {"content-type" => "application/json"}, [JSON.generate(result)]]
19
+ end
20
+ end
21
+
22
+ Rackup::Handler.default.run(SecondRemoteApp.new, :Port => 3002)