graphql-remote_loader 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +3 -34
- data/lib/graphql/remote_loader/loader.rb +15 -7
- data/lib/graphql/remote_loader/query_merger.rb +3 -0
- data/lib/graphql/remote_loader/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbd2e637ecc93810900d66de559f52ed874b5b08
|
4
|
+
data.tar.gz: 0fd8533f7f4514e6e18216311298eec7485bce18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cd9ba1245a71ba2643c9e65b7b0fd461e830f911a4d69e29a214eb0412d459638d9571514e4a683cc01c25919220d23b5ae326e975a55d2127e1367a4553835
|
7
|
+
data.tar.gz: a20d45c2bac56cf499bc88bcb2e94f66db84c924ae136a94cf81747d95bbeb196798f891f7aedf8d04c0c5ab4c86c0b6ab4d9b8f0af452bae35d0ce2feafa0c5
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
graphql-remote_loader (1.0.
|
4
|
+
graphql-remote_loader (1.0.1)
|
5
5
|
graphql (~> 1.6)
|
6
6
|
graphql-batch (~> 0.3)
|
7
7
|
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
byebug (9.1.0)
|
12
12
|
coderay (1.1.2)
|
13
13
|
diff-lcs (1.3)
|
14
|
-
graphql (1.
|
14
|
+
graphql (1.8.4)
|
15
15
|
graphql-batch (0.3.9)
|
16
16
|
graphql (>= 0.8, < 2)
|
17
17
|
promise.rb (~> 0.7.2)
|
data/README.md
CHANGED
@@ -45,39 +45,6 @@ A promise-based resolution strategy from Shopify's [`graphql-batch`](https://git
|
|
45
45
|
|
46
46
|
You can think of it as a lightweight version of schema-stitching.
|
47
47
|
|
48
|
-
## How it works
|
49
|
-
|
50
|
-
Using GraphQL batch, the loader collects all GraphQL sub-queries before combining and executing one query.
|
51
|
-
|
52
|
-
### Merging queries
|
53
|
-
|
54
|
-
Once we have all the queries we need, we merge them all together by treating the query ASTs as prefix trees and merging the prefix trees together. For example, if the input queries were `["viewer { foo bar }", "buzz viewer { foo bazz }"]`, then the merged query would be:
|
55
|
-
|
56
|
-
```graphql
|
57
|
-
query{
|
58
|
-
buzz
|
59
|
-
viewer{
|
60
|
-
foo
|
61
|
-
bar
|
62
|
-
bazz
|
63
|
-
}
|
64
|
-
}
|
65
|
-
```
|
66
|
-
|
67
|
-
### Name conflicts
|
68
|
-
|
69
|
-
Suppose we want to merge `["user(login: "foo") { url }", "user(login: "bar") { url }"]`. We can't treat these `user` fields as equal since their arguments don't match, but we also can't naively combine these queries together, the `user` key will be ambiguous.
|
70
|
-
|
71
|
-
To solve this problem, we introduce identified aliases. Every query to be merged is given a unique ID. When building the query, we include this unique ID in a new alias on all fields. This prevents conflicts between separate subqueries.
|
72
|
-
|
73
|
-
### Fulfilling promises
|
74
|
-
|
75
|
-
We only want to fulfill each promise with the data it asked for. This is especially relevant in the case of conflicts as in the case above.
|
76
|
-
|
77
|
-
When fulfilling promises, we traverse the result hash and prune out all result keys with UIDs that don't match the query. Before returning the filtered hash, we scrub the UIDs so that the result keys are the same as the GraphQL field names.
|
78
|
-
|
79
|
-
This solution prevents all conflict issues and doesn't change the way your API uses this library.
|
80
|
-
|
81
48
|
## How to use
|
82
49
|
First, you'll need to install the gem. Either do `gem install graphql-remote_loader` or add this to your Gemfile:
|
83
50
|
|
@@ -106,9 +73,11 @@ end
|
|
106
73
|
|
107
74
|
This example uses [`graphql-client`](https://github.com/github/graphql-client). Any client, or even just plain `cURL`/`HTTP` can be used.
|
108
75
|
|
76
|
+
With your loader setup, you can begin using `#load` or `#load_value` in your `graphql-ruby` resolvers.
|
77
|
+
|
109
78
|
## Running tests
|
110
79
|
|
111
80
|
```
|
112
81
|
bundle install
|
113
|
-
rspec
|
82
|
+
bundle exec rspec
|
114
83
|
```
|
@@ -11,21 +11,23 @@ module GraphQL
|
|
11
11
|
# a) Avoid name collisions in the generated query
|
12
12
|
# b) Determine which fields in the result JSON should be
|
13
13
|
# handed fulfilled to each promise
|
14
|
-
def self.load(query)
|
14
|
+
def self.load(query, context: {})
|
15
15
|
@index ||= 1
|
16
16
|
@index += 1
|
17
17
|
|
18
18
|
prime = Prime.take(@index - 1).last
|
19
19
|
|
20
|
-
|
20
|
+
store_context(context)
|
21
|
+
|
22
|
+
self.for.load([query, prime, @context])
|
21
23
|
end
|
22
24
|
|
23
25
|
# Loads the value, then if the query was successful, fulfills promise with
|
24
26
|
# the leaf value instead of the full results hash.
|
25
27
|
#
|
26
28
|
# If errors are present, returns nil.
|
27
|
-
def self.load_value(*path)
|
28
|
-
load(query_from_path(path)).then do |results|
|
29
|
+
def self.load_value(*path, context: {})
|
30
|
+
load(query_from_path(path), context: context).then do |results|
|
29
31
|
next nil if results["errors"] && !results["errors"].empty?
|
30
32
|
|
31
33
|
value_from_hash(results["data"])
|
@@ -36,6 +38,11 @@ module GraphQL
|
|
36
38
|
@index = nil
|
37
39
|
end
|
38
40
|
|
41
|
+
def self.store_context(context)
|
42
|
+
@context ||= {}
|
43
|
+
@context.merge!(context.to_h)
|
44
|
+
end
|
45
|
+
|
39
46
|
# Given a query string, return a response JSON
|
40
47
|
def query(query_string)
|
41
48
|
raise NotImplementedError,
|
@@ -46,11 +53,12 @@ module GraphQL
|
|
46
53
|
|
47
54
|
def perform(queries_and_primes)
|
48
55
|
query_string = QueryMerger.merge(queries_and_primes)
|
49
|
-
|
56
|
+
context = queries_and_primes[-1][-1]
|
57
|
+
response = query(query_string, context: context).to_h
|
50
58
|
|
51
59
|
data, errors = response["data"], response["errors"]
|
52
60
|
|
53
|
-
queries_and_primes.each do |query, prime|
|
61
|
+
queries_and_primes.each do |query, prime, context|
|
54
62
|
response = {}
|
55
63
|
|
56
64
|
response["data"] = filter_keys_on_data(data, prime)
|
@@ -60,7 +68,7 @@ module GraphQL
|
|
60
68
|
|
61
69
|
scrub_primes_from_error_paths!(response["errors"])
|
62
70
|
|
63
|
-
fulfill([query, prime], response)
|
71
|
+
fulfill([query, prime, context], response)
|
64
72
|
end
|
65
73
|
end
|
66
74
|
|
@@ -59,6 +59,9 @@ module GraphQL
|
|
59
59
|
|
60
60
|
a_query.selections.each do |a_query_selection|
|
61
61
|
matching_field = b_query.selections.find do |b_query_selection|
|
62
|
+
next false if (a_query_selection.is_a? GraphQL::Language::Nodes::InlineFragment) &&
|
63
|
+
(b_query_selection.is_a? GraphQL::Language::Nodes::InlineFragment)
|
64
|
+
|
62
65
|
same_name = a_query_selection.name == b_query_selection.name
|
63
66
|
|
64
67
|
next same_name if exempt_node_types.any? { |type| b_query_selection.is_a?(type) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-remote_loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathaniel Woodthorpe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|