graphql-stitching 1.5.0 → 1.5.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.
- checksums.yaml +4 -4
- data/README.md +7 -6
- data/docs/client.md +6 -0
- data/docs/subscriptions.md +6 -6
- data/docs/{resolver.md → type_resolver.md} +3 -3
- data/examples/subscriptions/app/graphql/subscriptions_schema.rb +3 -3
- data/lib/graphql/stitching/composer/{resolver_config.rb → type_resolver_config.rb} +2 -2
- data/lib/graphql/stitching/composer/{validate_resolvers.rb → validate_type_resolvers.rb} +1 -1
- data/lib/graphql/stitching/composer.rb +21 -18
- data/lib/graphql/stitching/executor/shaper.rb +2 -2
- data/lib/graphql/stitching/executor/{resolver_source.rb → type_resolver_source.rb} +2 -2
- data/lib/graphql/stitching/executor.rb +2 -2
- data/lib/graphql/stitching/planner/step.rb +1 -1
- data/lib/graphql/stitching/planner.rb +10 -12
- data/lib/graphql/stitching/request/skip_include.rb +1 -1
- data/lib/graphql/stitching/request.rb +2 -2
- data/lib/graphql/stitching/supergraph/to_definition.rb +3 -3
- data/lib/graphql/stitching/supergraph.rb +1 -1
- data/lib/graphql/stitching/{resolver → type_resolver}/arguments.rb +6 -6
- data/lib/graphql/stitching/{resolver → type_resolver}/keys.rb +1 -1
- data/lib/graphql/stitching/{resolver.rb → type_resolver.rb} +4 -4
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +15 -3
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8fb3561ddb47de2d07bdcdf32e940e1a151dff731081b2b4bffe09489809021
|
4
|
+
data.tar.gz: 9163e0d0e5116e232ee0594821d02568f85405c7fdc0f80e085e713d2e4d3a71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6823d661a3f812fbf11bbf14edf66f3da27a850399f909061c63d464d3be93777fe7f9c7158ea26cc0d1889aed04749d3ad10cd42c3ca76079604447ab66e3cc
|
7
|
+
data.tar.gz: 27dee9de11b90e960fb0c17d68129332ede38d237ef37ae1eca4cdca2c83696dec4b6c9f0283ee7f508c4f5076029afc81efa4b58d60e5b74411a6a368092947
|
data/README.md
CHANGED
@@ -5,12 +5,12 @@ GraphQL stitching composes a single schema from multiple underlying GraphQL reso
|
|
5
5
|

|
6
6
|
|
7
7
|
**Supports:**
|
8
|
-
- All operation types: query, mutation, and subscription.
|
8
|
+
- All operation types: query, mutation, and [subscription](./docs/subscriptions.md).
|
9
9
|
- Merged object and abstract types.
|
10
10
|
- Shared objects, fields, enums, and inputs across locations.
|
11
11
|
- Multiple and composite type keys.
|
12
12
|
- Combining local and remote schemas.
|
13
|
-
- File uploads via
|
13
|
+
- [File uploads](./docs/http_executable.md) via multipart forms.
|
14
14
|
- Tested with all minor versions of `graphql-ruby`.
|
15
15
|
|
16
16
|
**NOT Supported:**
|
@@ -35,7 +35,7 @@ require "graphql/stitching"
|
|
35
35
|
|
36
36
|
## Usage
|
37
37
|
|
38
|
-
The
|
38
|
+
The [`Client`](./docs/client.md) component builds a stitched graph wrapped in an executable workflow (with optional query plan caching hooks):
|
39
39
|
|
40
40
|
```ruby
|
41
41
|
movies_schema = <<~GRAPHQL
|
@@ -75,7 +75,7 @@ result = client.execute(
|
|
75
75
|
|
76
76
|
Schemas provided in [location settings](./docs/composer.md#performing-composition) may be class-based schemas with local resolvers (locally-executable schemas), or schemas built from SDL strings (schema definition language parsed using `GraphQL::Schema.from_definition`) and mapped to remote locations via [executables](#executables).
|
77
77
|
|
78
|
-
While
|
78
|
+
While `Client` is sufficient for most usecases, the library offers several discrete components that can be assembled into tailored workflows:
|
79
79
|
|
80
80
|
- [Composer](./docs/composer.md) - merges and validates many schemas into one supergraph.
|
81
81
|
- [Supergraph](./docs/supergraph.md) - manages the combined schema, location routing maps, and executable resources. Can be exported, cached, and rehydrated.
|
@@ -88,7 +88,7 @@ While the `Client` constructor is an easy quick start, the library also has seve
|
|
88
88
|
|
89
89
|

|
90
90
|
|
91
|
-
To facilitate this merging of types, stitching must know how to cross-reference and fetch each variant of a type from its source location using [resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).
|
91
|
+
To facilitate this merging of types, stitching must know how to cross-reference and fetch each variant of a type from its source location using [type resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).
|
92
92
|
|
93
93
|
### Merged type resolver queries
|
94
94
|
|
@@ -249,7 +249,7 @@ type Query {
|
|
249
249
|
}
|
250
250
|
```
|
251
251
|
|
252
|
-
See [resolver arguments](./docs/
|
252
|
+
See [resolver arguments](./docs/type_resolver.md#arguments) for full documentation on shaping input.
|
253
253
|
|
254
254
|
#### Composite type keys
|
255
255
|
|
@@ -458,6 +458,7 @@ This repo includes working examples of stitched schemas running across small Rac
|
|
458
458
|
|
459
459
|
- [Merged types](./examples/merged_types)
|
460
460
|
- [File uploads](./examples/file_uploads)
|
461
|
+
- [Subscriptions](./examples/subscriptions)
|
461
462
|
|
462
463
|
## Tests
|
463
464
|
|
data/docs/client.md
CHANGED
@@ -68,6 +68,12 @@ client.on_cache_write do |request, payload|
|
|
68
68
|
end
|
69
69
|
```
|
70
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
|
+
|
71
77
|
Note that inlined input data works against caching, so you should _avoid_ these input literals when possible:
|
72
78
|
|
73
79
|
```graphql
|
data/docs/subscriptions.md
CHANGED
@@ -88,7 +88,7 @@ class EntitiesSchema < GraphQL::Schema
|
|
88
88
|
end
|
89
89
|
```
|
90
90
|
|
91
|
-
These schemas can be composed as normal into a stitching client. The subscriptions schema must be locally-executable while other entity schema(s) may be served from anywhere:
|
91
|
+
These schemas can be composed as normal into a stitching client. The subscriptions schema must be locally-executable while the other entity schema(s) may be served from anywhere:
|
92
92
|
|
93
93
|
```ruby
|
94
94
|
StitchedSchema = GraphQL::Stitching::Client.new(locations: {
|
@@ -104,7 +104,7 @@ StitchedSchema = GraphQL::Stitching::Client.new(locations: {
|
|
104
104
|
|
105
105
|
### Serving stitched subscriptions
|
106
106
|
|
107
|
-
Once you've
|
107
|
+
Once you've composed a schema with subscriptions, it gets called as part of three workflows:
|
108
108
|
|
109
109
|
1. Controller - handles normal query and mutation requests recieved via HTTP.
|
110
110
|
2. Channel - handles subscription-create requests recieved through a socket connection.
|
@@ -172,7 +172,7 @@ class GraphqlChannel < ApplicationCable::Channel
|
|
172
172
|
end
|
173
173
|
```
|
174
174
|
|
175
|
-
What happens behind the scenes here is that stitching filters the `execute` request down to just subscription selections, and passes those through to the subscriptions subschema where they register an event binding. The subscriber response gets stitched while passing back
|
175
|
+
What happens behind the scenes here is that stitching filters the `execute` request down to just subscription selections, and passes those through to the subscriptions subschema where they register an event binding. The subscriber response gets stitched while passing back out through the stitching client.
|
176
176
|
|
177
177
|
#### Plugin
|
178
178
|
|
@@ -181,9 +181,9 @@ Lastly, update events trigger with the filtered subscriptions selection, so must
|
|
181
181
|
```ruby
|
182
182
|
class StitchedActionCableSubscriptions < GraphQL::Subscriptions::ActionCableSubscriptions
|
183
183
|
def execute_update(subscription_id, event, object)
|
184
|
-
super(subscription_id, event, object)
|
185
|
-
|
186
|
-
|
184
|
+
result = super(subscription_id, event, object)
|
185
|
+
result.context[:stitch_subscription_update]&.call(result)
|
186
|
+
result
|
187
187
|
end
|
188
188
|
end
|
189
189
|
|
@@ -1,10 +1,10 @@
|
|
1
|
-
## GraphQL::Stitching::
|
1
|
+
## GraphQL::Stitching::TypeResolver
|
2
2
|
|
3
|
-
A `
|
3
|
+
A `TypeResolver` contains all information about a root query used by stitching to fetch location-specific variants of a merged type. Specifically, resolvers manage parsed keys and argument structures.
|
4
4
|
|
5
5
|
### Arguments
|
6
6
|
|
7
|
-
|
7
|
+
Type resolvers configure arguments through a template string of [GraphQL argument literal syntax](https://spec.graphql.org/October2021/#sec-Language.Arguments). This allows sending multiple arguments that intermix stitching keys with complex object shapes and other static values.
|
8
8
|
|
9
9
|
#### Key insertions
|
10
10
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class SubscriptionsSchema < GraphQL::Schema
|
2
2
|
class StitchedActionCableSubscriptions < GraphQL::Subscriptions::ActionCableSubscriptions
|
3
3
|
def execute_update(subscription_id, event, object)
|
4
|
-
super(subscription_id, event, object)
|
5
|
-
|
6
|
-
|
4
|
+
result = super(subscription_id, event, object)
|
5
|
+
result.context[:stitch_subscription_update]&.call(result)
|
6
|
+
result
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
4
|
class Composer
|
5
|
-
class
|
5
|
+
class TypeResolverConfig
|
6
6
|
ENTITY_TYPENAME = "_Entity"
|
7
7
|
ENTITIES_QUERY = "_entities"
|
8
8
|
|
@@ -30,7 +30,7 @@ module GraphQL::Stitching
|
|
30
30
|
entity_type.directives.each do |directive|
|
31
31
|
next unless directive.graphql_name == "key"
|
32
32
|
|
33
|
-
key =
|
33
|
+
key = TypeResolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
|
34
34
|
key_fields = key.map { "#{_1.name}: $.#{_1.name}" }
|
35
35
|
field_path = "#{location}._entities"
|
36
36
|
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require_relative "composer/base_validator"
|
4
4
|
require_relative "composer/validate_interfaces"
|
5
|
-
require_relative "composer/
|
6
|
-
require_relative "composer/
|
5
|
+
require_relative "composer/validate_type_resolvers"
|
6
|
+
require_relative "composer/type_resolver_config"
|
7
7
|
|
8
8
|
module GraphQL
|
9
9
|
module Stitching
|
@@ -31,7 +31,7 @@ module GraphQL
|
|
31
31
|
# @api private
|
32
32
|
COMPOSITION_VALIDATORS = [
|
33
33
|
ValidateInterfaces,
|
34
|
-
|
34
|
+
ValidateTypeResolvers,
|
35
35
|
].freeze
|
36
36
|
|
37
37
|
# @return [String] name of the Query type in the composed schema.
|
@@ -168,7 +168,7 @@ module GraphQL
|
|
168
168
|
end
|
169
169
|
|
170
170
|
select_root_field_locations(schema)
|
171
|
-
expand_abstract_resolvers(schema)
|
171
|
+
expand_abstract_resolvers(schema, schemas)
|
172
172
|
|
173
173
|
supergraph = Supergraph.new(
|
174
174
|
schema: schema,
|
@@ -199,8 +199,8 @@ module GraphQL
|
|
199
199
|
raise CompositionError, "The schema for `#{location}` location must be a GraphQL::Schema class."
|
200
200
|
end
|
201
201
|
|
202
|
-
@resolver_configs.merge!(
|
203
|
-
@resolver_configs.merge!(
|
202
|
+
@resolver_configs.merge!(TypeResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
|
203
|
+
@resolver_configs.merge!(TypeResolverConfig.extract_federation_entities(schema, location))
|
204
204
|
|
205
205
|
schemas[location.to_s] = schema
|
206
206
|
executables[location.to_s] = input[:executable] || schema
|
@@ -546,13 +546,13 @@ module GraphQL
|
|
546
546
|
|
547
547
|
subgraph_field.directives.each do |directive|
|
548
548
|
next unless directive.graphql_name == GraphQL::Stitching.stitch_directive
|
549
|
-
resolver_configs <<
|
549
|
+
resolver_configs << TypeResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
|
550
550
|
end
|
551
551
|
|
552
552
|
resolver_configs.each do |config|
|
553
553
|
resolver_type_name = if config.type_name
|
554
554
|
if !resolver_type.kind.abstract?
|
555
|
-
raise CompositionError, "
|
555
|
+
raise CompositionError, "Type resolver config may only specify a type name for abstract resolvers."
|
556
556
|
elsif !resolver_type.possible_types.find { _1.graphql_name == config.type_name }
|
557
557
|
raise CompositionError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
|
558
558
|
end
|
@@ -561,7 +561,7 @@ module GraphQL
|
|
561
561
|
resolver_type.graphql_name
|
562
562
|
end
|
563
563
|
|
564
|
-
key =
|
564
|
+
key = TypeResolver.parse_key_with_types(
|
565
565
|
config.key,
|
566
566
|
@subgraph_types_by_name_and_location[resolver_type_name],
|
567
567
|
)
|
@@ -581,11 +581,11 @@ module GraphQL
|
|
581
581
|
"#{argument.graphql_name}: $.#{key.primitive_name}"
|
582
582
|
end
|
583
583
|
|
584
|
-
arguments =
|
584
|
+
arguments = TypeResolver.parse_arguments_with_field(arguments_format, subgraph_field)
|
585
585
|
arguments.each { _1.verify_key(key) }
|
586
586
|
|
587
587
|
@resolver_map[resolver_type_name] ||= []
|
588
|
-
@resolver_map[resolver_type_name] <<
|
588
|
+
@resolver_map[resolver_type_name] << TypeResolver.new(
|
589
589
|
location: location,
|
590
590
|
type_name: resolver_type_name,
|
591
591
|
field: subgraph_field.name,
|
@@ -620,15 +620,18 @@ module GraphQL
|
|
620
620
|
|
621
621
|
# @!scope class
|
622
622
|
# @!visibility private
|
623
|
-
def expand_abstract_resolvers(
|
623
|
+
def expand_abstract_resolvers(composed_schema, schemas_by_location)
|
624
624
|
@resolver_map.keys.each do |type_name|
|
625
|
-
|
626
|
-
next unless resolver_type.kind.abstract?
|
625
|
+
next unless composed_schema.get_type(type_name).kind.abstract?
|
627
626
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
627
|
+
@resolver_map[type_name].each do |resolver|
|
628
|
+
abstract_type = @subgraph_types_by_name_and_location[type_name][resolver.location]
|
629
|
+
expanded_types = Util.expand_abstract_type(schemas_by_location[resolver.location], abstract_type)
|
630
|
+
|
631
|
+
expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |impl_type|
|
632
|
+
@resolver_map[impl_type.graphql_name] ||= []
|
633
|
+
@resolver_map[impl_type.graphql_name].push(resolver)
|
634
|
+
end
|
632
635
|
end
|
633
636
|
end
|
634
637
|
end
|
@@ -23,8 +23,8 @@ module GraphQL::Stitching
|
|
23
23
|
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
|
24
24
|
return nil if raw_object.nil?
|
25
25
|
|
26
|
-
typename ||= raw_object[
|
27
|
-
raw_object.reject! { |key, _v|
|
26
|
+
typename ||= raw_object[TypeResolver::TYPENAME_EXPORT_NODE.alias]
|
27
|
+
raw_object.reject! { |key, _v| TypeResolver.export_key?(key) }
|
28
28
|
|
29
29
|
selections.each do |node|
|
30
30
|
case node
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
4
|
class Executor
|
5
|
-
class
|
5
|
+
class TypeResolverSource < GraphQL::Dataloader::Source
|
6
6
|
def initialize(executor, location)
|
7
7
|
@executor = executor
|
8
8
|
@location = location
|
@@ -17,7 +17,7 @@ module GraphQL::Stitching
|
|
17
17
|
|
18
18
|
if op.if_type
|
19
19
|
# operations planned around unused fragment conditions should not trigger requests
|
20
|
-
origin_set.select! { _1[
|
20
|
+
origin_set.select! { _1[TypeResolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
|
21
21
|
end
|
22
22
|
|
23
23
|
memo[op] = origin_set if origin_set.any?
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
-
require_relative "executor/resolver_source"
|
5
4
|
require_relative "executor/root_source"
|
5
|
+
require_relative "executor/type_resolver_source"
|
6
6
|
require_relative "executor/shaper"
|
7
7
|
|
8
8
|
module GraphQL
|
@@ -66,7 +66,7 @@ module GraphQL
|
|
66
66
|
.select { next_steps.include?(_1.after) }
|
67
67
|
.group_by { [_1.location, _1.resolver.nil?] }
|
68
68
|
.map do |(location, root_source), ops|
|
69
|
-
source_class = root_source ? RootSource :
|
69
|
+
source_class = root_source ? RootSource : TypeResolverSource
|
70
70
|
@dataloader.with(source_class, self, location).request_all(ops)
|
71
71
|
end
|
72
72
|
|
@@ -35,6 +35,7 @@ module GraphQL
|
|
35
35
|
# A) Group all root selections by their preferred entrypoint locations.
|
36
36
|
# A.1) Group query fields by location for parallel execution.
|
37
37
|
# A.2) Partition mutation fields by consecutive location for serial execution.
|
38
|
+
# A.3) Permit exactly one subscription field.
|
38
39
|
#
|
39
40
|
# B) Extract contiguous selections for each entrypoint location.
|
40
41
|
# B.1) Selections on interface types that do not belong to the interface at the
|
@@ -75,7 +76,7 @@ module GraphQL
|
|
75
76
|
resolver: nil
|
76
77
|
)
|
77
78
|
# coalesce repeat parameters into a single entrypoint
|
78
|
-
entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key&.to_definition}")
|
79
|
+
entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key&.to_definition}/#")
|
79
80
|
path.each { entrypoint << "/#{_1}" }
|
80
81
|
|
81
82
|
step = @steps_by_entrypoint[entrypoint]
|
@@ -107,11 +108,11 @@ module GraphQL
|
|
107
108
|
|
108
109
|
# A) Group all root selections by their preferred entrypoint locations.
|
109
110
|
def build_root_entrypoints
|
111
|
+
parent_type = @supergraph.schema.root_type_for_operation(@request.operation.operation_type)
|
112
|
+
|
110
113
|
case @request.operation.operation_type
|
111
114
|
when QUERY_OP
|
112
115
|
# A.1) Group query fields by location for parallel execution.
|
113
|
-
parent_type = @supergraph.schema.query
|
114
|
-
|
115
116
|
selections_by_location = {}
|
116
117
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
117
118
|
locations = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name] || SUPERGRAPH_LOCATIONS
|
@@ -131,8 +132,6 @@ module GraphQL
|
|
131
132
|
|
132
133
|
when MUTATION_OP
|
133
134
|
# A.2) Partition mutation fields by consecutive location for serial execution.
|
134
|
-
parent_type = @supergraph.schema.mutation
|
135
|
-
|
136
135
|
partitions = []
|
137
136
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
138
137
|
next_location = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name].first
|
@@ -155,8 +154,7 @@ module GraphQL
|
|
155
154
|
end
|
156
155
|
|
157
156
|
when SUBSCRIPTION_OP
|
158
|
-
|
159
|
-
|
157
|
+
# A.3) Permit exactly one subscription field.
|
160
158
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
161
159
|
raise StitchingError, "Too many root fields for subscription." unless @steps_by_entrypoint.empty?
|
162
160
|
|
@@ -217,8 +215,8 @@ module GraphQL
|
|
217
215
|
input_selections.each do |node|
|
218
216
|
case node
|
219
217
|
when GraphQL::Language::Nodes::Field
|
220
|
-
if node.alias&.start_with?(
|
221
|
-
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{
|
218
|
+
if node.alias&.start_with?(TypeResolver::EXPORT_PREFIX)
|
219
|
+
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{TypeResolver::EXPORT_PREFIX}" is a reserved prefix.)
|
222
220
|
elsif node.name == TYPENAME
|
223
221
|
locale_selections << node
|
224
222
|
next
|
@@ -279,8 +277,8 @@ module GraphQL
|
|
279
277
|
|
280
278
|
# B.4) Add a `__typename` export to abstracts and types that implement
|
281
279
|
# fragments so that resolved type information is available during execution.
|
282
|
-
if requires_typename && !locale_selections.include?(
|
283
|
-
locale_selections <<
|
280
|
+
if requires_typename && !locale_selections.include?(TypeResolver::TYPENAME_EXPORT_NODE)
|
281
|
+
locale_selections << TypeResolver::TYPENAME_EXPORT_NODE
|
284
282
|
end
|
285
283
|
|
286
284
|
if remote_selections
|
@@ -296,7 +294,7 @@ module GraphQL
|
|
296
294
|
# E.1) Add the key of each resolver query into the prior location's selection set.
|
297
295
|
parent_selections.push(*resolver.key.export_nodes) if resolver.key
|
298
296
|
parent_selections.uniq! do |node|
|
299
|
-
export_node = node.is_a?(GraphQL::Language::Nodes::Field) &&
|
297
|
+
export_node = node.is_a?(GraphQL::Language::Nodes::Field) && TypeResolver.export_key?(node.alias)
|
300
298
|
export_node ? node.alias : node.object_id
|
301
299
|
end
|
302
300
|
|
@@ -75,12 +75,12 @@ module GraphQL
|
|
75
75
|
|
76
76
|
# @return [String] a digest of the original document string. Generally faster but less consistent.
|
77
77
|
def digest
|
78
|
-
@digest ||=
|
78
|
+
@digest ||= Stitching.digest.call("#{Stitching::VERSION}/#{string}")
|
79
79
|
end
|
80
80
|
|
81
81
|
# @return [String] a digest of the normalized document string. Slower but more consistent.
|
82
82
|
def normalized_digest
|
83
|
-
@normalized_digest ||=
|
83
|
+
@normalized_digest ||= Stitching.digest.call("#{Stitching::VERSION}/#{normalized_string}")
|
84
84
|
end
|
85
85
|
|
86
86
|
# @return [GraphQL::Language::Nodes::OperationDefinition] The selected root operation for the request.
|
@@ -32,7 +32,7 @@ module GraphQL::Stitching
|
|
32
32
|
end
|
33
33
|
|
34
34
|
key_definitions = locations_by_key.each_with_object({}) do |(key, locations), memo|
|
35
|
-
memo[key] =
|
35
|
+
memo[key] = TypeResolver.parse_key(key, locations)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Collect/build resolver definitions for each type
|
@@ -41,13 +41,13 @@ module GraphQL::Stitching
|
|
41
41
|
|
42
42
|
kwargs = directive.arguments.keyword_arguments
|
43
43
|
resolver_map[type_name] ||= []
|
44
|
-
resolver_map[type_name] <<
|
44
|
+
resolver_map[type_name] << TypeResolver.new(
|
45
45
|
location: kwargs[:location],
|
46
46
|
type_name: kwargs.fetch(:type_name, type_name),
|
47
47
|
field: kwargs[:field],
|
48
48
|
list: kwargs[:list] || false,
|
49
49
|
key: key_definitions[kwargs[:key]],
|
50
|
-
arguments:
|
50
|
+
arguments: TypeResolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
|
51
51
|
)
|
52
52
|
end
|
53
53
|
|
@@ -166,7 +166,7 @@ module GraphQL
|
|
166
166
|
if key_count.zero?
|
167
167
|
# nested root scopes have no resolver keys and just return a location
|
168
168
|
goal_locations.each_with_object({}) do |goal_location, memo|
|
169
|
-
memo[goal_location] = [
|
169
|
+
memo[goal_location] = [TypeResolver.new(location: goal_location)]
|
170
170
|
end
|
171
171
|
|
172
172
|
elsif key_count > 1
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
|
-
class
|
4
|
+
class TypeResolver
|
5
5
|
# Defines a single resolver argument structure
|
6
6
|
# @api private
|
7
7
|
class Argument
|
@@ -129,9 +129,9 @@ module GraphQL::Stitching
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def verify_key(arg, key)
|
132
|
-
key_field = value.reduce(
|
132
|
+
key_field = value.reduce(TypeResolver::KeyField.new("", inner: key)) do |field, ns|
|
133
133
|
if ns == TYPENAME
|
134
|
-
|
134
|
+
TypeResolver::KeyField.new(TYPENAME)
|
135
135
|
elsif field
|
136
136
|
field.inner.find { _1.name == ns }
|
137
137
|
end
|
@@ -146,7 +146,7 @@ module GraphQL::Stitching
|
|
146
146
|
|
147
147
|
def build(origin_obj)
|
148
148
|
value.each_with_index.reduce(origin_obj) do |obj, (ns, idx)|
|
149
|
-
obj[idx.zero? ?
|
149
|
+
obj[idx.zero? ? TypeResolver.export_key(ns) : ns]
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
@@ -174,7 +174,7 @@ module GraphQL::Stitching
|
|
174
174
|
# Parses an argument template string into resolver arguments via schema casting.
|
175
175
|
# @param template [String] the template string to parse.
|
176
176
|
# @param field_def [GraphQL::Schema::FieldDefinition] a field definition providing arguments schema.
|
177
|
-
# @return [[GraphQL::Stitching::
|
177
|
+
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
|
178
178
|
def parse_arguments_with_field(template, field_def)
|
179
179
|
ast = parse_arg_defs(template)
|
180
180
|
args = build_argument_set(ast, field_def.arguments)
|
@@ -196,7 +196,7 @@ module GraphQL::Stitching
|
|
196
196
|
# Parses an argument template string into resolver arguments via SDL casting.
|
197
197
|
# @param template [String] the template string to parse.
|
198
198
|
# @param type_defs [String] the type definition string declaring argument types.
|
199
|
-
# @return [[GraphQL::Stitching::
|
199
|
+
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
|
200
200
|
def parse_arguments_with_type_defs(template, type_defs)
|
201
201
|
type_map = parse_type_defs(type_defs)
|
202
202
|
parse_arg_defs(template).map { build_argument(_1, type_struct: type_map[_1.name]) }
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
3
|
+
require_relative "type_resolver/arguments"
|
4
|
+
require_relative "type_resolver/keys"
|
5
5
|
|
6
6
|
module GraphQL
|
7
7
|
module Stitching
|
8
8
|
# Defines a type resolver query that provides direct access to an entity type.
|
9
|
-
class
|
9
|
+
class TypeResolver
|
10
10
|
extend ArgumentsParser
|
11
11
|
extend KeysParser
|
12
12
|
|
@@ -47,7 +47,7 @@ module GraphQL
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def version
|
50
|
-
@version ||=
|
50
|
+
@version ||= Stitching.digest.call("#{Stitching::VERSION}/#{as_json.to_json}")
|
51
51
|
end
|
52
52
|
|
53
53
|
def ==(other)
|
data/lib/graphql/stitching.rb
CHANGED
@@ -27,12 +27,24 @@ module GraphQL
|
|
27
27
|
class ValidationError < CompositionError; end
|
28
28
|
|
29
29
|
class << self
|
30
|
+
attr_writer :stitch_directive
|
31
|
+
|
32
|
+
# Proc used to compute digests; uses SHA2 by default.
|
33
|
+
# @returns [Proc] proc used to compute digests.
|
34
|
+
def digest(&block)
|
35
|
+
if block_given?
|
36
|
+
@digest = block
|
37
|
+
else
|
38
|
+
@digest ||= ->(str) { Digest::SHA2.hexdigest(str) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Name of the directive used to mark type resolvers.
|
43
|
+
# @returns [String] name of the type resolver directive.
|
30
44
|
def stitch_directive
|
31
45
|
@stitch_directive ||= "stitch"
|
32
46
|
end
|
33
47
|
|
34
|
-
attr_writer :stitch_directive
|
35
|
-
|
36
48
|
# Names of stitching directives to omit from the composed supergraph.
|
37
49
|
# @returns [Array<String>] list of stitching directive names.
|
38
50
|
def stitching_directive_names
|
@@ -50,6 +62,6 @@ require_relative "stitching/http_executable"
|
|
50
62
|
require_relative "stitching/plan"
|
51
63
|
require_relative "stitching/planner"
|
52
64
|
require_relative "stitching/request"
|
53
|
-
require_relative "stitching/
|
65
|
+
require_relative "stitching/type_resolver"
|
54
66
|
require_relative "stitching/util"
|
55
67
|
require_relative "stitching/version"
|
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.5.
|
4
|
+
version: 1.5.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: 2024-
|
11
|
+
date: 2024-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -89,9 +89,9 @@ files:
|
|
89
89
|
- docs/images/stitching.png
|
90
90
|
- docs/mechanics.md
|
91
91
|
- docs/request.md
|
92
|
-
- docs/resolver.md
|
93
92
|
- docs/subscriptions.md
|
94
93
|
- docs/supergraph.md
|
94
|
+
- docs/type_resolver.md
|
95
95
|
- examples/file_uploads/Gemfile
|
96
96
|
- examples/file_uploads/Procfile
|
97
97
|
- examples/file_uploads/README.md
|
@@ -160,27 +160,27 @@ files:
|
|
160
160
|
- lib/graphql/stitching/client.rb
|
161
161
|
- lib/graphql/stitching/composer.rb
|
162
162
|
- lib/graphql/stitching/composer/base_validator.rb
|
163
|
-
- lib/graphql/stitching/composer/
|
163
|
+
- lib/graphql/stitching/composer/type_resolver_config.rb
|
164
164
|
- lib/graphql/stitching/composer/validate_interfaces.rb
|
165
|
-
- lib/graphql/stitching/composer/
|
165
|
+
- lib/graphql/stitching/composer/validate_type_resolvers.rb
|
166
166
|
- lib/graphql/stitching/executor.rb
|
167
|
-
- lib/graphql/stitching/executor/resolver_source.rb
|
168
167
|
- lib/graphql/stitching/executor/root_source.rb
|
169
168
|
- lib/graphql/stitching/executor/shaper.rb
|
169
|
+
- lib/graphql/stitching/executor/type_resolver_source.rb
|
170
170
|
- lib/graphql/stitching/http_executable.rb
|
171
171
|
- lib/graphql/stitching/plan.rb
|
172
172
|
- lib/graphql/stitching/planner.rb
|
173
173
|
- lib/graphql/stitching/planner/step.rb
|
174
174
|
- lib/graphql/stitching/request.rb
|
175
175
|
- lib/graphql/stitching/request/skip_include.rb
|
176
|
-
- lib/graphql/stitching/resolver.rb
|
177
|
-
- lib/graphql/stitching/resolver/arguments.rb
|
178
|
-
- lib/graphql/stitching/resolver/keys.rb
|
179
176
|
- lib/graphql/stitching/supergraph.rb
|
180
177
|
- lib/graphql/stitching/supergraph/key_directive.rb
|
181
178
|
- lib/graphql/stitching/supergraph/resolver_directive.rb
|
182
179
|
- lib/graphql/stitching/supergraph/source_directive.rb
|
183
180
|
- lib/graphql/stitching/supergraph/to_definition.rb
|
181
|
+
- lib/graphql/stitching/type_resolver.rb
|
182
|
+
- lib/graphql/stitching/type_resolver/arguments.rb
|
183
|
+
- lib/graphql/stitching/type_resolver/keys.rb
|
184
184
|
- lib/graphql/stitching/util.rb
|
185
185
|
- lib/graphql/stitching/version.rb
|
186
186
|
homepage: https://github.com/gmac/graphql-stitching-ruby
|