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.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/Gemfile +0 -3
- data/README.md +10 -32
- data/docs/README.md +1 -0
- data/docs/client.md +2 -2
- data/docs/composer.md +1 -1
- data/docs/executor.md +8 -15
- data/docs/http_executable.md +51 -0
- data/docs/planner.md +12 -14
- data/docs/request.md +2 -0
- data/docs/supergraph.md +2 -2
- data/examples/file_uploads/Gemfile +9 -0
- data/examples/file_uploads/Procfile +2 -0
- data/examples/file_uploads/README.md +37 -0
- data/examples/file_uploads/file.txt +1 -0
- data/examples/file_uploads/gateway.rb +37 -0
- data/examples/file_uploads/helpers.rb +62 -0
- data/examples/file_uploads/remote.rb +21 -0
- data/examples/merged_types/Gemfile +8 -0
- data/examples/merged_types/Procfile +3 -0
- data/examples/merged_types/README.md +33 -0
- data/{example → examples/merged_types}/gateway.rb +4 -5
- data/examples/merged_types/remote1.rb +22 -0
- data/examples/merged_types/remote2.rb +22 -0
- data/lib/graphql/stitching/client.rb +9 -19
- data/lib/graphql/stitching/composer/base_validator.rb +3 -3
- data/lib/graphql/stitching/composer/validate_boundaries.rb +3 -3
- data/lib/graphql/stitching/composer/validate_interfaces.rb +3 -4
- data/lib/graphql/stitching/composer.rb +66 -9
- data/lib/graphql/stitching/executor/boundary_source.rb +4 -6
- data/lib/graphql/stitching/executor/root_source.rb +4 -4
- data/lib/graphql/stitching/executor.rb +16 -13
- data/lib/graphql/stitching/export_selection.rb +6 -1
- data/lib/graphql/stitching/http_executable.rb +145 -4
- data/lib/graphql/stitching/planner.rb +3 -3
- data/lib/graphql/stitching/request.rb +66 -4
- data/lib/graphql/stitching/shaper.rb +4 -3
- data/lib/graphql/stitching/skip_include.rb +4 -3
- data/lib/graphql/stitching/supergraph/resolver_directive.rb +17 -0
- data/lib/graphql/stitching/supergraph/source_directive.rb +12 -0
- data/lib/graphql/stitching/supergraph.rb +27 -34
- data/lib/graphql/stitching/util.rb +0 -9
- data/lib/graphql/stitching/version.rb +1 -1
- metadata +20 -7
- data/Procfile +0 -3
- data/example/remote1.rb +0 -26
- data/example/remote2.rb +0 -26
- /data/{example → examples/merged_types}/graphiql.html +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cb8c0d3b16cda5db8b82d0d0f9eba93a4537c3faa584b1b301dc84b34b5fd0e
|
4
|
+
data.tar.gz: 83f4b436479307ac8575f718ace5a82288024a6e5741f786151e55920d7ef61f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e247e539e223a1fbefcd398c1aeb3940fa91320b81c45caeaa21b3ddca50631ffb6ddbd3963cde07f11609cbaacc1f814a167fefebab8f888979afe929bcb93f
|
7
|
+
data.tar.gz: 66de3bacb73749bd90b4d8e7ade4bf35cc144e0ae60b3ff1c81a5f548b20d74e204454b6727875984da293b4b3853aa9bf6215283666f2b037a7dc148e8aa64b
|
data/.yardopts
ADDED
data/Gemfile
CHANGED
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(
|
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(
|
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: ->(
|
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
|
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
|
-
##
|
436
|
+
## Examples
|
435
437
|
|
436
|
-
This repo includes
|
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
|
-
|
439
|
-
|
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
|
-
$
|
63
|
+
$cache.get(request.digest) # << 3P code
|
64
64
|
end
|
65
65
|
|
66
66
|
client.on_cache_write do |request, payload|
|
67
|
-
$
|
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(
|
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
|
-
|
23
|
-
|
24
|
-
request: request,
|
25
|
-
).perform
|
23
|
+
# Via Request:
|
24
|
+
result = request.execute
|
26
25
|
|
27
|
-
|
28
|
-
|
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 =
|
41
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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 = $
|
34
|
+
cached_plan = $cache.get(request.digest)
|
33
35
|
|
34
|
-
|
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 =
|
38
|
-
|
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
|
-
$
|
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 = $
|
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,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,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
|
-
|
8
|
-
require_relative '
|
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
|
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
|
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)
|