graphql-stitching 0.0.1 → 0.1.0
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/.github/workflows/ci.yml +8 -2
- data/.gitignore +2 -0
- data/README.md +1 -2
- data/docs/executor.md +10 -0
- data/graphql-stitching.gemspec +2 -2
- data/lib/graphql/stitching/executor.rb +8 -3
- data/lib/graphql/stitching/shaper.rb +58 -54
- data/lib/graphql/stitching/util.rb +9 -1
- data/lib/graphql/stitching/version.rb +1 -1
- metadata +4 -7
- data/.ruby-version +0 -1
- data/Gemfile.lock +0 -49
- data/docs/shaper.md +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5090330cd8b46c9d7dbfc7bbc68f5840e97f8bcbc5ec710de219441237fb61e5
|
4
|
+
data.tar.gz: 846a15ccc2734024dec751d61d456aa05d4b7c396d6789d629da21e11c182100
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c0c16a9bb49b3fad30057958d79fe92737bb659bf7135dd03ef716597f013249c99d9372478e20619f54e1e77f68f3b7f746b92e9e220b80be9ecaf88b656de
|
7
|
+
data.tar.gz: f370e520552f3e8be141073dd7ca06f68a463824c946b9cd03357b33ad5c9fc928893e846314f08941e2590d24202081450677fbfca480474be9833d341209a1
|
data/.github/workflows/ci.yml
CHANGED
@@ -11,14 +11,20 @@ jobs:
|
|
11
11
|
runs-on: ubuntu-latest
|
12
12
|
strategy:
|
13
13
|
matrix:
|
14
|
-
|
14
|
+
include:
|
15
|
+
- gemfile: Gemfile
|
16
|
+
ruby: 3.2
|
17
|
+
- gemfile: Gemfile
|
18
|
+
ruby: 3.1
|
19
|
+
- gemfile: Gemfile
|
20
|
+
ruby: 2.7
|
15
21
|
|
16
22
|
steps:
|
17
23
|
- uses: actions/checkout@v2
|
18
24
|
- name: Setup Ruby
|
19
25
|
uses: ruby/setup-ruby@v1
|
20
26
|
with:
|
21
|
-
ruby-version: ${{ matrix.ruby
|
27
|
+
ruby-version: ${{ matrix.ruby }}
|
22
28
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
23
29
|
- name: Run tests
|
24
30
|
run: |
|
data/.gitignore
CHANGED
@@ -10,6 +10,7 @@
|
|
10
10
|
/test/version_tmp/
|
11
11
|
/tmp/
|
12
12
|
.envrc
|
13
|
+
Gemfile.lock
|
13
14
|
|
14
15
|
# Used by dotenv library to load environment variables.
|
15
16
|
# .env
|
@@ -17,6 +18,7 @@
|
|
17
18
|
|
18
19
|
# Ignore Byebug command history file.
|
19
20
|
.byebug_history
|
21
|
+
.ruby-version
|
20
22
|
.DS_Store
|
21
23
|
|
22
24
|
## Specific to RubyMotion:
|
data/README.md
CHANGED
@@ -46,7 +46,7 @@ showtimes_schema = <<~GRAPHQL
|
|
46
46
|
GRAPHQL
|
47
47
|
|
48
48
|
gateway = GraphQL::Stitching::Gateway.new(locations: {
|
49
|
-
|
49
|
+
movies: {
|
50
50
|
schema: GraphQL::Schema.from_definition(movies_schema),
|
51
51
|
executable: GraphQL::Stitching::RemoteClient.new(url: "http://localhost:3000"),
|
52
52
|
},
|
@@ -79,7 +79,6 @@ While the [`Gateway`](./docs/gateway.md) constructor is an easy quick start, the
|
|
79
79
|
- [Document](./docs/document.md) - manages a parsed GraphQL request document.
|
80
80
|
- [Planner](./docs/planner.md) - builds a cacheable query plan for a request document.
|
81
81
|
- [Executor](./docs/executor.md) - executes a query plan with given request variables.
|
82
|
-
- [Shaper](./docs/shaper.md) - takes the raw output of the executor and prepares it for delivery.
|
83
82
|
|
84
83
|
## Merged types
|
85
84
|
|
data/docs/executor.md
CHANGED
@@ -21,9 +21,19 @@ plan = GraphQL::Stitching::Planner.new(
|
|
21
21
|
document: document,
|
22
22
|
).perform
|
23
23
|
|
24
|
+
# get the raw result without shaping
|
24
25
|
raw_result = GraphQL::Stitching::Executor.new(
|
25
26
|
supergraph: supergraph,
|
26
27
|
plan: plan.to_h,
|
27
28
|
variables: variables,
|
28
29
|
).perform
|
30
|
+
|
31
|
+
# get the final result with shaping
|
32
|
+
final_result = GraphQL::Stitching::Executor.new(
|
33
|
+
supergraph: supergraph,
|
34
|
+
plan: plan.to_h,
|
35
|
+
variables: variables,
|
36
|
+
).perform(document)
|
29
37
|
```
|
38
|
+
|
39
|
+
Note that an executor's `perform` method accepts a document argument. When provided, the raw execution result will be shaped for delivery to match the document. Without a document, the raw result will be returned with stitching inclusions and no null bubbling applied.
|
data/graphql-stitching.gemspec
CHANGED
@@ -8,11 +8,11 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = GraphQL::Stitching::VERSION
|
9
9
|
spec.authors = ['Greg MacWilliam']
|
10
10
|
spec.summary = 'GraphQL schema stitching for Ruby'
|
11
|
-
spec.description =
|
11
|
+
spec.description = 'Combine GraphQL services into one unified graph'
|
12
12
|
spec.homepage = 'https://github.com/gmac/graphql-stitching-ruby'
|
13
13
|
spec.license = 'MIT'
|
14
14
|
|
15
|
-
spec.required_ruby_version = '>=
|
15
|
+
spec.required_ruby_version = '>= 2.7.0'
|
16
16
|
|
17
17
|
spec.metadata = {
|
18
18
|
'homepage_uri' => 'https://github.com/gmac/graphql-stitching-ruby',
|
@@ -219,9 +219,14 @@ module GraphQL
|
|
219
219
|
result["data"] = @data if @data && @data.length > 0
|
220
220
|
result["errors"] = @errors if @errors.length > 0
|
221
221
|
|
222
|
-
|
223
|
-
|
224
|
-
|
222
|
+
if document && result["data"]
|
223
|
+
GraphQL::Stitching::Shaper.new(
|
224
|
+
schema: @supergraph.schema,
|
225
|
+
document: document,
|
226
|
+
).perform!(result)
|
227
|
+
else
|
228
|
+
result
|
229
|
+
end
|
225
230
|
end
|
226
231
|
|
227
232
|
private
|
@@ -4,88 +4,92 @@
|
|
4
4
|
module GraphQL
|
5
5
|
module Stitching
|
6
6
|
class Shaper
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(schema:, document:)
|
8
|
+
@schema = schema
|
9
9
|
@document = document
|
10
|
-
@result = raw
|
11
|
-
@errors = []
|
12
10
|
end
|
13
11
|
|
14
|
-
def perform!
|
15
|
-
|
16
|
-
|
17
|
-
# hate doing a second pass, but cannot remove _STITCH_ fields until the fragements are processed
|
18
|
-
clean_entry(@result["data"])
|
19
|
-
end
|
20
|
-
|
21
|
-
if @errors.length > 0
|
22
|
-
@result = [] unless @result.key?("errors")
|
23
|
-
@result["errors"] += @errors
|
24
|
-
end
|
25
|
-
|
26
|
-
@result
|
12
|
+
def perform!(raw)
|
13
|
+
raw["data"] = resolve_object_scope(raw["data"], @schema.query, @document.operation.selections)
|
14
|
+
raw
|
27
15
|
end
|
28
16
|
|
29
17
|
private
|
30
18
|
|
31
|
-
def
|
19
|
+
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
|
20
|
+
return nil if raw_object.nil?
|
21
|
+
|
22
|
+
typename ||= raw_object["_STITCH_typename"]
|
23
|
+
raw_object.reject! { |k, _v| k.start_with?("_STITCH_") }
|
24
|
+
|
32
25
|
selections.each do |node|
|
33
26
|
case node
|
34
27
|
when GraphQL::Language::Nodes::Field
|
35
|
-
next if node.
|
28
|
+
next if node.name.start_with?("__")
|
36
29
|
|
37
|
-
|
30
|
+
field_name = node.alias || node.name
|
31
|
+
node_type = parent_type.fields[node.name].type
|
32
|
+
named_type = Util.get_named_type_for_field_node(@schema, parent_type, node)
|
33
|
+
|
34
|
+
raw_object[field_name] = if node_type.list?
|
35
|
+
resolve_list_scope(raw_object[field_name], Util.unwrap_non_null(node_type), node.selections)
|
36
|
+
elsif Util.is_leaf_type?(named_type)
|
37
|
+
raw_object[field_name]
|
38
|
+
else
|
39
|
+
resolve_object_scope(raw_object[field_name], named_type, node.selections)
|
40
|
+
end
|
41
|
+
|
42
|
+
return nil if raw_object[field_name].nil? && node_type.non_null?
|
38
43
|
|
39
44
|
when GraphQL::Language::Nodes::InlineFragment
|
40
|
-
next unless
|
41
|
-
fragment_type = @
|
42
|
-
|
45
|
+
next unless typename == node.type.name
|
46
|
+
fragment_type = @schema.types[node.type.name]
|
47
|
+
result = resolve_object_scope(raw_object, fragment_type, node.selections, typename)
|
48
|
+
return nil if result.nil?
|
43
49
|
|
44
50
|
when GraphQL::Language::Nodes::FragmentSpread
|
45
|
-
next unless entry["_STITCH_typename"] == node.name
|
46
51
|
fragment = @document.fragment_definitions[node.name]
|
47
|
-
fragment_type = @
|
48
|
-
|
52
|
+
fragment_type = @schema.types[fragment.type.name]
|
53
|
+
next unless typename == fragment_type.graphql_name
|
54
|
+
|
55
|
+
result = resolve_object_scope(raw_object, fragment_type, fragment.selections, typename)
|
56
|
+
return nil if result.nil?
|
57
|
+
|
49
58
|
else
|
50
59
|
raise "Unexpected node of type #{node.class.name} in selection set."
|
51
60
|
end
|
52
61
|
end
|
62
|
+
|
63
|
+
raw_object
|
53
64
|
end
|
54
65
|
|
55
|
-
def
|
56
|
-
|
57
|
-
node_type = Util.get_named_type_for_field_node(@supergraph.schema, parent_type, node)
|
66
|
+
def resolve_list_scope(raw_list, current_node_type, selections)
|
67
|
+
return nil if raw_list.nil?
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
next_node_type = Util.unwrap_non_null(current_node_type).of_type
|
70
|
+
named_type = Util.get_named_type(next_node_type)
|
71
|
+
contains_null = false
|
62
72
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
resolved_list = raw_list.map! do |raw_list_element|
|
74
|
+
result = if next_node_type.list?
|
75
|
+
resolve_list_scope(raw_list_element, next_node_type, selections)
|
76
|
+
elsif Util.is_leaf_type?(named_type)
|
77
|
+
raw_list_element
|
78
|
+
else
|
79
|
+
resolve_object_scope(raw_list_element, named_type, selections)
|
69
80
|
end
|
70
|
-
elsif ! Util.is_leaf_type?(node_type)
|
71
|
-
munge_entry(child_entry, node.selections, node_type)
|
72
|
-
end
|
73
|
-
end
|
74
81
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
entry.each do |key, value|
|
79
|
-
if key.start_with? "_STITCH_"
|
80
|
-
entry.delete(key)
|
81
|
-
elsif value.is_a?(Array)
|
82
|
-
value.each do |item|
|
83
|
-
clean_entry(item)
|
84
|
-
end
|
85
|
-
elsif value.is_a?(Hash)
|
86
|
-
clean_entry(value)
|
82
|
+
if result.nil?
|
83
|
+
contains_null = true
|
84
|
+
return nil if current_node_type.non_null?
|
87
85
|
end
|
86
|
+
|
87
|
+
result
|
88
88
|
end
|
89
|
+
|
90
|
+
return nil if contains_null && next_node_type.non_null?
|
91
|
+
|
92
|
+
resolved_list
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
module Stitching
|
5
5
|
class Util
|
6
6
|
|
7
|
-
# gets the named type at the bottom of a non-null/list wrapper
|
7
|
+
# gets the named type at the bottom of a non-null/list wrapper tree
|
8
8
|
def self.get_named_type(type)
|
9
9
|
while type.respond_to?(:of_type)
|
10
10
|
type = type.of_type
|
@@ -12,6 +12,14 @@ module GraphQL
|
|
12
12
|
type
|
13
13
|
end
|
14
14
|
|
15
|
+
# strips non-null wrappers from a type
|
16
|
+
def self.unwrap_non_null(type)
|
17
|
+
while type.is_a?(GraphQL::Schema::NonNull)
|
18
|
+
type = type.of_type
|
19
|
+
end
|
20
|
+
type
|
21
|
+
end
|
22
|
+
|
15
23
|
# gets a deep structural description of a list value type
|
16
24
|
def self.get_list_structure(type)
|
17
25
|
structure = []
|
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: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg MacWilliam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.12'
|
69
|
-
description: GraphQL
|
69
|
+
description: Combine GraphQL services into one unified graph
|
70
70
|
email:
|
71
71
|
executables: []
|
72
72
|
extensions: []
|
@@ -74,9 +74,7 @@ extra_rdoc_files: []
|
|
74
74
|
files:
|
75
75
|
- ".github/workflows/ci.yml"
|
76
76
|
- ".gitignore"
|
77
|
-
- ".ruby-version"
|
78
77
|
- Gemfile
|
79
|
-
- Gemfile.lock
|
80
78
|
- LICENSE
|
81
79
|
- Procfile
|
82
80
|
- README.md
|
@@ -90,7 +88,6 @@ files:
|
|
90
88
|
- docs/images/merging.png
|
91
89
|
- docs/images/stitching.png
|
92
90
|
- docs/planner.md
|
93
|
-
- docs/shaper.md
|
94
91
|
- docs/supergraph.md
|
95
92
|
- example/gateway.rb
|
96
93
|
- example/graphiql.html
|
@@ -128,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
125
|
requirements:
|
129
126
|
- - ">="
|
130
127
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
128
|
+
version: 2.7.0
|
132
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
130
|
requirements:
|
134
131
|
- - ">="
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.1.1
|
data/Gemfile.lock
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
graphql-stitching (0.0.1)
|
5
|
-
graphql (~> 2.0.16)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
byebug (11.1.3)
|
11
|
-
coderay (1.1.3)
|
12
|
-
foreman (0.87.2)
|
13
|
-
graphql (2.0.16)
|
14
|
-
method_source (1.0.0)
|
15
|
-
minitest (5.17.0)
|
16
|
-
pry (0.14.2)
|
17
|
-
coderay (~> 1.1)
|
18
|
-
method_source (~> 1.0)
|
19
|
-
pry-byebug (3.10.1)
|
20
|
-
byebug (~> 11.0)
|
21
|
-
pry (>= 0.13, < 0.15)
|
22
|
-
rack (3.0.4.1)
|
23
|
-
rackup (2.1.0)
|
24
|
-
rack (>= 3)
|
25
|
-
webrick (~> 1.8)
|
26
|
-
rake (12.3.3)
|
27
|
-
warning (1.3.0)
|
28
|
-
webrick (1.8.1)
|
29
|
-
|
30
|
-
PLATFORMS
|
31
|
-
arm64-darwin-21
|
32
|
-
ruby
|
33
|
-
x86_64-darwin-21
|
34
|
-
x86_64-linux
|
35
|
-
|
36
|
-
DEPENDENCIES
|
37
|
-
bundler (~> 2.0)
|
38
|
-
foreman
|
39
|
-
graphql-stitching!
|
40
|
-
minitest (~> 5.12)
|
41
|
-
pry
|
42
|
-
pry-byebug
|
43
|
-
rack
|
44
|
-
rackup
|
45
|
-
rake (~> 12.0)
|
46
|
-
warning
|
47
|
-
|
48
|
-
BUNDLED WITH
|
49
|
-
2.4.1
|
data/docs/shaper.md
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
## GraphQL::Stitching::Shaper
|
2
|
-
|
3
|
-
The `Shaper` takes the raw output generated by the `GraphQL::Stitching::Executor` and does the final shaping and
|
4
|
-
cleaning. It removes data that was added while building the result, it also handles cleaning up violations that can
|
5
|
-
only occur at the end, such as bubbling up null violoations.
|
6
|
-
|
7
|
-
See the [Executor](./docs/executor.md)
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
raw_result = GraphQL::Stitching::Executor.new(
|
11
|
-
supergraph: supergraph,
|
12
|
-
plan: plan.to_h,
|
13
|
-
variables: variables,
|
14
|
-
).perform(document)
|
15
|
-
|
16
|
-
final_result = GraphQL::Stitching::Shaper.new(
|
17
|
-
supergraph: supergraph,
|
18
|
-
document: document,
|
19
|
-
raw: raw_result).perform!
|
20
|
-
```
|