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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b71b1d9e65a84356466a0ff822e177ef5faccce1e2a0cbd91d930b06ea50923
4
- data.tar.gz: 8c73c1bcc78d23ea3b0c3f09da2659d84b7877000ccba86f79fa9f83f5801c60
3
+ metadata.gz: d8fb3561ddb47de2d07bdcdf32e940e1a151dff731081b2b4bffe09489809021
4
+ data.tar.gz: 9163e0d0e5116e232ee0594821d02568f85405c7fdc0f80e085e713d2e4d3a71
5
5
  SHA512:
6
- metadata.gz: 0e46b25856e61dc033ba0e2640d30133463c550af0071ac0e8bb3836b2fa8f4d0690018359b5b3b086861a907b5c66ea55eece9d4bfebe85296e723aedd96415
7
- data.tar.gz: 79180fc8944adde3ee0abf79a6fefa3487724cfbf9fb55e00adf29aa42d637c1c53331aad34a0c9c32ed7e3120fdaa6dd26fba9644df86b0feb774a84a8876b2
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
  ![Stitched graph](./docs/images/stitching.png)
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 [multipart form spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
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 quickest way to start is to use the provided [`Client`](./docs/client.md) component that wraps a stitched graph in an executable workflow (with optional query plan caching hooks):
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 the `Client` constructor is an easy quick start, the library also has several discrete components that can be assembled into custom workflows:
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
  ![Merging types](./docs/images/merging.png)
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/resolver.md#arguments) for full documentation on shaping input.
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
@@ -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 stitched a schema with subscriptions, it gets called as part of three workflows:
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 up through the stitching client.
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).tap do |result|
185
- result.context[:stitch_subscription_update]&.call(result)
186
- end
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::Resolver
1
+ ## GraphQL::Stitching::TypeResolver
2
2
 
3
- A `Resolver` 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.
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
- 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.
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).tap do |result|
5
- result.context[:stitch_subscription_update]&.call(result)
6
- end
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 ResolverConfig
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 = Resolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
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,7 +2,7 @@
2
2
 
3
3
  module GraphQL::Stitching
4
4
  class Composer
5
- class ValidateResolvers < BaseValidator
5
+ class ValidateTypeResolvers < BaseValidator
6
6
 
7
7
  def perform(supergraph, composer)
8
8
  root_types = [
@@ -2,8 +2,8 @@
2
2
 
3
3
  require_relative "composer/base_validator"
4
4
  require_relative "composer/validate_interfaces"
5
- require_relative "composer/validate_resolvers"
6
- require_relative "composer/resolver_config"
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
- ValidateResolvers,
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!(ResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
203
- @resolver_configs.merge!(ResolverConfig.extract_federation_entities(schema, location))
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 << ResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
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, "Resolver config may only specify a type name for abstract resolvers."
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 = Resolver.parse_key_with_types(
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 = Resolver.parse_arguments_with_field(arguments_format, subgraph_field)
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] << Resolver.new(
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(schema)
623
+ def expand_abstract_resolvers(composed_schema, schemas_by_location)
624
624
  @resolver_map.keys.each do |type_name|
625
- resolver_type = schema.types[type_name]
626
- next unless resolver_type.kind.abstract?
625
+ next unless composed_schema.get_type(type_name).kind.abstract?
627
626
 
628
- expanded_types = Util.expand_abstract_type(schema, resolver_type)
629
- expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |expanded_type|
630
- @resolver_map[expanded_type.graphql_name] ||= []
631
- @resolver_map[expanded_type.graphql_name].push(*@resolver_map[type_name])
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[Resolver::TYPENAME_EXPORT_NODE.alias]
27
- raw_object.reject! { |key, _v| Resolver.export_key?(key) }
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 ResolverSource < GraphQL::Dataloader::Source
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[Resolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
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 : ResolverSource
69
+ source_class = root_source ? RootSource : TypeResolverSource
70
70
  @dataloader.with(source_class, self, location).request_all(ops)
71
71
  end
72
72
 
@@ -16,7 +16,7 @@ module GraphQL::Stitching
16
16
  parent_type:,
17
17
  index:,
18
18
  after: nil,
19
- operation_type: "query",
19
+ operation_type: QUERY_OP,
20
20
  selections: [],
21
21
  variables: {},
22
22
  path: [],
@@ -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
- parent_type = @supergraph.schema.subscription
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?(Resolver::EXPORT_PREFIX)
221
- raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{Resolver::EXPORT_PREFIX}" is a reserved prefix.)
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?(Resolver::TYPENAME_EXPORT_NODE)
283
- locale_selections << Resolver::TYPENAME_EXPORT_NODE
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) && Resolver.export_key?(node.alias)
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
 
@@ -40,7 +40,7 @@ module GraphQL::Stitching
40
40
  end
41
41
 
42
42
  if filtered_selections.none?
43
- filtered_selections << Resolver::TYPENAME_EXPORT_NODE
43
+ filtered_selections << TypeResolver::TYPENAME_EXPORT_NODE
44
44
  end
45
45
 
46
46
  if changed
@@ -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 ||= Digest::SHA2.hexdigest(string)
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 ||= Digest::SHA2.hexdigest(normalized_string)
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] = Resolver.parse_key(key, locations)
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] << Resolver.new(
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: Resolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
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] = [Resolver.new(location: 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 Resolver
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(Resolver::KeyField.new("", inner: key)) do |field, ns|
132
+ key_field = value.reduce(TypeResolver::KeyField.new("", inner: key)) do |field, ns|
133
133
  if ns == TYPENAME
134
- Resolver::KeyField.new(TYPENAME)
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? ? Resolver.export_key(ns) : ns]
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::Resolver::Argument]] an array of resolver arguments.
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::Resolver::Argument]] an array of resolver arguments.
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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphQL::Stitching
4
- class Resolver
4
+ class TypeResolver
5
5
  EXPORT_PREFIX = "_export_"
6
6
 
7
7
  class FieldNode
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "resolver/arguments"
4
- require_relative "resolver/keys"
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 Resolver
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 ||= Digest::SHA2.hexdigest("#{Stitching::VERSION}/#{as_json.to_json}")
50
+ @version ||= Stitching.digest.call("#{Stitching::VERSION}/#{as_json.to_json}")
51
51
  end
52
52
 
53
53
  def ==(other)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Stitching
5
- VERSION = "1.5.0"
5
+ VERSION = "1.5.1"
6
6
  end
7
7
  end
@@ -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/resolver"
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.0
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-07-25 00:00:00.000000000 Z
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/resolver_config.rb
163
+ - lib/graphql/stitching/composer/type_resolver_config.rb
164
164
  - lib/graphql/stitching/composer/validate_interfaces.rb
165
- - lib/graphql/stitching/composer/validate_resolvers.rb
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